From c9f33a6f2aee04bb8f0109ee8688cb870b200077 Mon Sep 17 00:00:00 2001 From: Rudra Gupta Date: Mon, 23 Oct 2023 18:13:28 -0400 Subject: [PATCH 1/4] feat: multi region replica strike implementation --- strikes/AutomatedBackups.go | 35 ++------------ strikes/MultiRegion.go | 96 +++++++++++++++++++++++++++++++++++++ strikes/MultiRegion_test.go | 32 +++++++++++++ strikes/common.go | 58 +++++++++++++++++++++- 4 files changed, 190 insertions(+), 31 deletions(-) create mode 100644 strikes/MultiRegion.go create mode 100644 strikes/MultiRegion_test.go diff --git a/strikes/AutomatedBackups.go b/strikes/AutomatedBackups.go index 967f1f3..a4246fd 100644 --- a/strikes/AutomatedBackups.go +++ b/strikes/AutomatedBackups.go @@ -23,7 +23,7 @@ func (a *Strikes) AutomatedBackups() (strikeName string, result raidengine.Strik Movements: make(map[string]raidengine.MovementResult), } - // Movement + // Get Configuration cfg, err := getAWSConfig() if err != nil { result.Message = err.Error() @@ -37,10 +37,10 @@ func (a *Strikes) AutomatedBackups() (strikeName string, result raidengine.Strik return } - autmatedBackupsMovement := checkRDSAutomatedBackupMovement(cfg) - result.Movements["CheckForDBInstanceAutomatedBackups"] = autmatedBackupsMovement - if !autmatedBackupsMovement.Passed { - result.Message = autmatedBackupsMovement.Message + automatedBackupsMovement := checkRDSAutomatedBackupMovement(cfg) + result.Movements["CheckForDBInstanceAutomatedBackups"] = automatedBackupsMovement + if !automatedBackupsMovement.Passed { + result.Message = automatedBackupsMovement.Message return } @@ -49,31 +49,6 @@ func (a *Strikes) AutomatedBackups() (strikeName string, result raidengine.Strik return } -func checkRDSInstanceMovement(cfg aws.Config) (result raidengine.MovementResult) { - // check if the instance is available - result = raidengine.MovementResult{ - Description: "Check if the instance is available/exists", - Function: utils.CallerPath(0), - } - - rdsClient := rds.NewFromConfig(cfg) - identifier, _ := getDBInstanceIdentifier() - - input := &rds.DescribeDBInstancesInput{ - DBInstanceIdentifier: aws.String(identifier), - } - - instances, err := rdsClient.DescribeDBInstances(context.TODO(), input) - if err != nil { - // Handle error - result.Message = err.Error() - result.Passed = false - return - } - result.Passed = len(instances.DBInstances) > 0 - return -} - func checkRDSAutomatedBackupMovement(cfg aws.Config) (result raidengine.MovementResult) { result = raidengine.MovementResult{ diff --git a/strikes/MultiRegion.go b/strikes/MultiRegion.go new file mode 100644 index 0000000..2373e4d --- /dev/null +++ b/strikes/MultiRegion.go @@ -0,0 +1,96 @@ +package strikes + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/rds" + "github.com/privateerproj/privateer-sdk/raidengine" + "github.com/privateerproj/privateer-sdk/utils" +) + +func (a *Strikes) MultiRegion() (strikeName string, result raidengine.StrikeResult) { + strikeName = "MultiRegion" + result = raidengine.StrikeResult{ + Passed: false, + Description: "Check if AWS RDS instance has multi region. This strike only checks for a read replica in a seperate region", + DocsURL: "https://www.github.com/krumIO/raid-rds", + ControlID: "CCC-Taxonomy-1", + Movements: make(map[string]raidengine.MovementResult), + } + + // Get Configuration + cfg, err := getAWSConfig() + if err != nil { + result.Message = err.Error() + return + } + + rdsInstanceMovement := checkRDSInstanceMovement(cfg) + result.Movements["CheckForDBInstance"] = rdsInstanceMovement + if !rdsInstanceMovement.Passed { + result.Message = rdsInstanceMovement.Message + return + } + + multiRegionMovement := checkRDSMultiRegionMovement(cfg) + result.Movements["CheckForMultiRegionDBInstances"] = multiRegionMovement + if !multiRegionMovement.Passed { + result.Message = multiRegionMovement.Message + return + } + + result.Passed = true + result.Message = "Completed Successfully" + + return +} + +func checkRDSMultiRegionMovement(cfg aws.Config) (result raidengine.MovementResult) { + + result = raidengine.MovementResult{ + Description: "Check if the instance has multi region enabled", + Function: utils.CallerPath(0), + } + + rdsClient := rds.NewFromConfig(cfg) + identifier, _ := getDBInstanceIdentifier() + + input := &rds.DescribeDBInstanceAutomatedBackupsInput{ + DBInstanceIdentifier: aws.String(identifier), + } + + backups, err := rdsClient.DescribeDBInstanceAutomatedBackups(context.TODO(), input) + if err != nil { + result.Message = "Failed to fetch automated backups for instance " + identifier + result.Passed = false + return + } + + var regions []string + for _, backup := range backups.DBInstanceAutomatedBackups { + regions = append(regions, *backup.Region) + } + + // This checks if theres a read replica in a different region + if len(regions) > 0 { + hostDBRegion := getRDSRegion() + for _, region := range regions { + // region from the instances are in the form of "use2" + abbreviation, exists := AWS_REGIONS_ABBR[hostDBRegion] + if exists { + if region != abbreviation { + result.Passed = true + result.Message = "Completed Successfully" + return + } + } + + } + } + + result.Passed = false + result.Message = "Multi Region instances not found" + return + +} diff --git a/strikes/MultiRegion_test.go b/strikes/MultiRegion_test.go new file mode 100644 index 0000000..34b6c67 --- /dev/null +++ b/strikes/MultiRegion_test.go @@ -0,0 +1,32 @@ +package strikes + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/spf13/viper" +) + +func TestMultiRegion(t *testing.T) { + viper.AddConfigPath("../") + viper.SetConfigName("config") + viper.SetConfigType("yaml") + err := viper.ReadInConfig() + + if err != nil { + fmt.Println("Config file not found...") + return + } + + strikes := Strikes{} + strikeName, result := strikes.MultiRegion() + + fmt.Println(strikeName) + b, err := json.MarshalIndent(result, "", " ") + if err != nil { + fmt.Println(err) + } + fmt.Print(string(b)) + fmt.Println() +} diff --git a/strikes/common.go b/strikes/common.go index 0530851..73b8c96 100644 --- a/strikes/common.go +++ b/strikes/common.go @@ -7,12 +7,33 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/rds" hclog "github.com/hashicorp/go-hclog" "github.com/privateerproj/privateer-sdk/raidengine" "github.com/privateerproj/privateer-sdk/utils" "github.com/spf13/viper" ) +var ( + AWS_REGIONS_ABBR = map[string]string{ + "us-east-1": "use1", + "us-east-2": "use2", + "us-west-1": "usw1", + "us-west-2": "usw2", + "ca-central-1": "cac1", + "eu-west-1": "euw1", + "eu-west-2": "euw2", + "eu-central-1": "euc1", + "eu-north-1": "eun1", + "ap-northeast-1": "apne1", + "ap-northeast-2": "apne2", + "ap-southeast-1": "apse1", + "ap-southeast-2": "apse2", + "ap-south-1": "aps1", + "sa-east-1": "sae1", + } +) + type Strikes struct { Log hclog.Logger } @@ -39,10 +60,15 @@ func getDBInstanceIdentifier() (string, error) { return "", errors.New("database instance identifier must be set in the config file") } +func getRDSRegion() string { + return viper.GetString("raids.RDS.aws.config.region") +} + func getAWSConfig() (cfg aws.Config, err error) { if viper.IsSet("raids.RDS.aws.creds") && viper.IsSet("raids.RDS.aws.creds.aws_access_key") && - viper.IsSet("raids.RDS.aws.creds.aws_secret_key") { + viper.IsSet("raids.RDS.aws.creds.aws_secret_key") && + viper.IsSet("raids.RDS.aws.creds.aws_region") { access_key := viper.GetString("raids.RDS.aws.creds.aws_access_key") secret_key := viper.GetString("raids.RDS.aws.creds.aws_secret_key") @@ -68,3 +94,33 @@ func connectToDb() (result raidengine.MovementResult) { result.Passed = true return } + +func checkRDSInstanceMovement(cfg aws.Config) (result raidengine.MovementResult) { + // check if the instance is available + result = raidengine.MovementResult{ + Description: "Check if the instance is available/exists", + Function: utils.CallerPath(0), + } + + instance, err := getRDSInstance(cfg) + if err != nil { + // Handle error + result.Message = err.Error() + result.Passed = false + return + } + result.Passed = len(instance.DBInstances) > 0 + return +} + +func getRDSInstance(cfg aws.Config) (instance *rds.DescribeDBInstancesOutput, err error) { + rdsClient := rds.NewFromConfig(cfg) + identifier, _ := getDBInstanceIdentifier() + + input := &rds.DescribeDBInstancesInput{ + DBInstanceIdentifier: aws.String(identifier), + } + + instance, err = rdsClient.DescribeDBInstances(context.TODO(), input) + return +} From bb513eac8c7392f8a85a3a3cdcc8870f072f22bc Mon Sep 17 00:00:00 2001 From: Rudra Gupta Date: Mon, 23 Oct 2023 18:16:11 -0400 Subject: [PATCH 2/4] feat: adds multi region strike in the available strike map --- cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index d8c94f9..b05dea9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -25,9 +25,9 @@ var ( "CCC-Taxonomy": { Strikes.SQLFeatures, Strikes.AutomatedBackups, + Strikes.MultiRegion, // Strikes.VerticalScaling, // Strikes.Replication, - // Strikes.MultiRegion, // Strikes.BackupRecovery, // Strikes.Encryption, // Strikes.RBAC, From 3a8c466dcee4785394d2aa80e46c6dd097c45360 Mon Sep 17 00:00:00 2001 From: Rudra Gupta Date: Tue, 24 Oct 2023 12:29:46 -0400 Subject: [PATCH 3/4] fix: fix issue with replica logic --- example-config.yml | 1 + strikes/AutomatedBackups.go | 4 +-- strikes/MultiRegion.go | 62 +++++++++++++++++++------------------ strikes/common.go | 36 ++++++--------------- 4 files changed, 45 insertions(+), 58 deletions(-) diff --git a/example-config.yml b/example-config.yml index 66597a0..58725bb 100644 --- a/example-config.yml +++ b/example-config.yml @@ -11,6 +11,7 @@ raids: config: instance_identifier: unique-id-name database: test + region: us-east-1 host: localhost password: password port: 3306 diff --git a/strikes/AutomatedBackups.go b/strikes/AutomatedBackups.go index a4246fd..a4ae071 100644 --- a/strikes/AutomatedBackups.go +++ b/strikes/AutomatedBackups.go @@ -57,10 +57,10 @@ func checkRDSAutomatedBackupMovement(cfg aws.Config) (result raidengine.Movement } rdsClient := rds.NewFromConfig(cfg) - identifier, _ := getDBInstanceIdentifier() + instanceIdentifier, _ := getHostDBInstanceIdentifier() input := &rds.DescribeDBInstanceAutomatedBackupsInput{ - DBInstanceIdentifier: aws.String(identifier), + DBInstanceIdentifier: aws.String(instanceIdentifier), } backups, err := rdsClient.DescribeDBInstanceAutomatedBackups(context.TODO(), input) diff --git a/strikes/MultiRegion.go b/strikes/MultiRegion.go index 2373e4d..ac2adba 100644 --- a/strikes/MultiRegion.go +++ b/strikes/MultiRegion.go @@ -1,10 +1,7 @@ package strikes import ( - "context" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/rds" "github.com/privateerproj/privateer-sdk/raidengine" "github.com/privateerproj/privateer-sdk/utils" ) @@ -52,45 +49,50 @@ func checkRDSMultiRegionMovement(cfg aws.Config) (result raidengine.MovementResu Description: "Check if the instance has multi region enabled", Function: utils.CallerPath(0), } + instanceIdentifier, _ := getHostDBInstanceIdentifier() - rdsClient := rds.NewFromConfig(cfg) - identifier, _ := getDBInstanceIdentifier() + instance, _ := getRDSInstanceFromIdentifier(cfg, instanceIdentifier) - input := &rds.DescribeDBInstanceAutomatedBackupsInput{ - DBInstanceIdentifier: aws.String(identifier), - } + // get read replicas from the instance + readReplicas := instance.DBInstances[0].ReadReplicaDBInstanceIdentifiers - backups, err := rdsClient.DescribeDBInstanceAutomatedBackups(context.TODO(), input) - if err != nil { - result.Message = "Failed to fetch automated backups for instance " + identifier + if len(readReplicas) == 0 { result.Passed = false + result.Message = "Multi Region instances not found" return } - var regions []string - for _, backup := range backups.DBInstanceAutomatedBackups { - regions = append(regions, *backup.Region) - } + hostRDSRegion, _ := getHostRDSRegion() - // This checks if theres a read replica in a different region - if len(regions) > 0 { - hostDBRegion := getRDSRegion() - for _, region := range regions { - // region from the instances are in the form of "use2" - abbreviation, exists := AWS_REGIONS_ABBR[hostDBRegion] - if exists { - if region != abbreviation { - result.Passed = true - result.Message = "Completed Successfully" - return - } - } + // loop over the read replicas and check if they are in a different region + for _, replica := range readReplicas { + // we are getting the instance identifier the read replicas + // get instance from the replica identifier + replicaInstance, err := getRDSInstanceFromIdentifier(cfg, replica) + if err != nil { + result.Passed = false + result.Message = err.Error() + return + } + + if len(replicaInstance.DBInstances) == 0 { + result.Passed = false + result.Message = "Cannot access the replica instance " + replica + return + } + + // check if replica region matches the host region + az := *replicaInstance.DBInstances[0].AvailabilityZone + // db instance doesnt contain the region so we need to remove the last character from the az + if az[:len(az)-1] == hostRDSRegion { + result.Passed = false + result.Message = "Multi Region instances not found" + return } } - result.Passed = false - result.Message = "Multi Region instances not found" + result.Passed = true return } diff --git a/strikes/common.go b/strikes/common.go index 73b8c96..31efcf2 100644 --- a/strikes/common.go +++ b/strikes/common.go @@ -14,26 +14,6 @@ import ( "github.com/spf13/viper" ) -var ( - AWS_REGIONS_ABBR = map[string]string{ - "us-east-1": "use1", - "us-east-2": "use2", - "us-west-1": "usw1", - "us-west-2": "usw2", - "ca-central-1": "cac1", - "eu-west-1": "euw1", - "eu-west-2": "euw2", - "eu-central-1": "euc1", - "eu-north-1": "eun1", - "ap-northeast-1": "apne1", - "ap-northeast-2": "apne2", - "ap-southeast-1": "apse1", - "ap-southeast-2": "apse2", - "ap-south-1": "aps1", - "sa-east-1": "sae1", - } -) - type Strikes struct { Log hclog.Logger } @@ -53,15 +33,18 @@ func getDBConfig() (string, error) { return "", errors.New("database url must be set in the config file") } -func getDBInstanceIdentifier() (string, error) { +func getHostDBInstanceIdentifier() (string, error) { if viper.IsSet("raids.RDS.aws.config.instance_identifier") { return viper.GetString("raids.RDS.aws.config.instance_identifier"), nil } return "", errors.New("database instance identifier must be set in the config file") } -func getRDSRegion() string { - return viper.GetString("raids.RDS.aws.config.region") +func getHostRDSRegion() (string, error) { + if viper.IsSet("raids.RDS.aws.config.region") { + return viper.GetString("raids.RDS.aws.config.region"), nil + } + return "", errors.New("database instance identifier must be set in the config file") } func getAWSConfig() (cfg aws.Config, err error) { @@ -102,7 +85,9 @@ func checkRDSInstanceMovement(cfg aws.Config) (result raidengine.MovementResult) Function: utils.CallerPath(0), } - instance, err := getRDSInstance(cfg) + instanceIdentifier, _ := getHostDBInstanceIdentifier() + + instance, err := getRDSInstanceFromIdentifier(cfg, instanceIdentifier) if err != nil { // Handle error result.Message = err.Error() @@ -113,9 +98,8 @@ func checkRDSInstanceMovement(cfg aws.Config) (result raidengine.MovementResult) return } -func getRDSInstance(cfg aws.Config) (instance *rds.DescribeDBInstancesOutput, err error) { +func getRDSInstanceFromIdentifier(cfg aws.Config, identifier string) (instance *rds.DescribeDBInstancesOutput, err error) { rdsClient := rds.NewFromConfig(cfg) - identifier, _ := getDBInstanceIdentifier() input := &rds.DescribeDBInstancesInput{ DBInstanceIdentifier: aws.String(identifier), From 914cab4a91685c5ba34d64cc689ce924934d2679 Mon Sep 17 00:00:00 2001 From: Rudra Gupta Date: Thu, 26 Oct 2023 10:23:03 -0400 Subject: [PATCH 4/4] fix: change region to primary_region for clarity --- example-config.yml | 2 +- strikes/common.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example-config.yml b/example-config.yml index 58725bb..436075b 100644 --- a/example-config.yml +++ b/example-config.yml @@ -11,7 +11,7 @@ raids: config: instance_identifier: unique-id-name database: test - region: us-east-1 + primary_region: us-east-1 host: localhost password: password port: 3306 diff --git a/strikes/common.go b/strikes/common.go index 31efcf2..22f3520 100644 --- a/strikes/common.go +++ b/strikes/common.go @@ -41,8 +41,8 @@ func getHostDBInstanceIdentifier() (string, error) { } func getHostRDSRegion() (string, error) { - if viper.IsSet("raids.RDS.aws.config.region") { - return viper.GetString("raids.RDS.aws.config.region"), nil + if viper.IsSet("raids.RDS.aws.config.primary_region") { + return viper.GetString("raids.RDS.aws.config.primary_region"), nil } return "", errors.New("database instance identifier must be set in the config file") }