diff --git a/lib/services/database.go b/lib/services/database.go index 498796ae616..41c7e6f5890 100644 --- a/lib/services/database.go +++ b/lib/services/database.go @@ -19,8 +19,10 @@ package services import ( "context" "fmt" + "strings" "github.com/gravitational/teleport/api/types" + apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/utils" @@ -225,6 +227,49 @@ func tagsToLabels(tags []*rds.Tag) map[string]string { return labels } +// IsRDSClusterSupported checks whether the aurora cluster is supported and logs +// related info if not. +func IsRDSClusterSupported(cluster *rds.DBCluster) bool { + switch aws.StringValue(cluster.EngineMode) { + // Aurora Serverless (v1 and v2) does not support IAM authentication + // https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless.html#aurora-serverless.limitations + // https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-2.limitations.html + case RDSEngineModeServerless: + return false + + // Aurora MySQL 1.22.2, 1.20.1, 1.19.6, and 5.6.10a only: Parallel query doesn't support AWS Identity and Access Management (IAM) database authentication. + // https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-mysql-parallel-query.html#aurora-mysql-parallel-query-limitations + case RDSEngineModeParallelQuery: + if apiutils.SliceContainsStr([]string{"1.22.2", "1.20.1", "1.19.6", "5.6.10a"}, auroraMySQLVersion(cluster)) { + return false + } + } + + return true +} + +// auroraMySQLVersion extracts aurora mysql version from engine version +func auroraMySQLVersion(cluster *rds.DBCluster) string { + // version guide: https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Updates.Versions.html + // a list of all the available versions: https://docs.aws.amazon.com/cli/latest/reference/rds/describe-db-engine-versions.html + // + // some examples of possible inputs: + // 5.6.10a + // 5.7.12 + // 5.6.mysql_aurora.1.22.0 + // 5.6.mysql_aurora.1.22.1 + // 5.6.mysql_aurora.1.22.1.3 + // + // general format is: .mysql_aurora. + // 5.6.10a and 5.7.12 are "legacy" versions and they are returned as it is + version := aws.StringValue(cluster.EngineVersion) + parts := strings.Split(version, ".mysql_aurora.") + if len(parts) == 2 { + return parts[1] + } + return version +} + const ( // labelAccountID is the label key containing AWS account ID. labelAccountID = "account-id" @@ -248,3 +293,16 @@ const ( // RDSEngineAuroraPostgres is RDS engine name for Aurora Postgres clusters. RDSEngineAuroraPostgres = "aurora-postgresql" ) + +const ( + // RDSEngineModeProvisioned is the RDS engine mode for provisioned Aurora clusters + RDSEngineModeProvisioned = "provisioned" + // RDSEngineModeServerless is the RDS engine mode for Aurora Serverless DB clusters + RDSEngineModeServerless = "serverless" + // RDSEngineModeParallelQuery is the RDS engine mode for Aurora MySQL clusters with parallel query enabled + RDSEngineModeParallelQuery = "parallelquery" + // RDSEngineModeGlobal is the RDS engine mode for Aurora Global databases + RDSEngineModeGlobal = "global" + // RDSEngineModeMultiMaster is the RDS engine mode for Multi-master clusters + RDSEngineModeMultiMaster = "multimaster" +) diff --git a/lib/services/database_test.go b/lib/services/database_test.go index e84c3450328..cde3eb208db 100644 --- a/lib/services/database_test.go +++ b/lib/services/database_test.go @@ -26,6 +26,7 @@ import ( "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/fixtures" "github.com/gravitational/teleport/lib/utils" + "github.com/pborman/uuid" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/rds" @@ -189,3 +190,78 @@ func TestDatabaseFromRDSCluster(t *testing.T) { require.NoError(t, err) require.Equal(t, expected, actual) } + +func TestAuroraMySQLVersion(t *testing.T) { + tests := []struct { + engineVersion string + expectedMySQLVersion string + }{ + { + engineVersion: "5.6.10a", + expectedMySQLVersion: "5.6.10a", + }, + { + engineVersion: "5.6.mysql_aurora.1.22.1", + expectedMySQLVersion: "1.22.1", + }, + { + engineVersion: "5.6.mysql_aurora.1.22.1.3", + expectedMySQLVersion: "1.22.1.3", + }, + } + for _, test := range tests { + t.Run(test.engineVersion, func(t *testing.T) { + require.Equal(t, test.expectedMySQLVersion, auroraMySQLVersion(&rds.DBCluster{EngineVersion: aws.String(test.engineVersion)})) + }) + } +} + +func TestIsRDSClusterSupported(t *testing.T) { + tests := []struct { + name string + engineMode string + engineVersion string + isSupported bool + }{ + { + name: "provisioned", + engineMode: RDSEngineModeProvisioned, + engineVersion: "5.6.mysql_aurora.1.22.0", + isSupported: true, + }, + { + name: "serverless", + engineMode: RDSEngineModeServerless, + engineVersion: "5.6.mysql_aurora.1.22.0", + isSupported: false, + }, + { + name: "parallel query supported", + engineMode: RDSEngineModeParallelQuery, + engineVersion: "5.6.mysql_aurora.1.22.0", + isSupported: true, + }, + { + name: "parallel query unsupported", + engineMode: RDSEngineModeParallelQuery, + engineVersion: "5.6.mysql_aurora.1.19.6", + isSupported: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cluster := &rds.DBCluster{ + DBClusterArn: aws.String("arn:aws:rds:us-east-1:1234567890:cluster:test"), + DBClusterIdentifier: aws.String(test.name), + DbClusterResourceId: aws.String(uuid.New()), + Engine: aws.String(RDSEngineAuroraMySQL), + EngineMode: aws.String(test.engineMode), + EngineVersion: aws.String(test.engineVersion), + } + + require.Equal(t, test.isSupported, IsRDSClusterSupported(cluster)) + + }) + } +} diff --git a/lib/srv/db/cloud/watchers/rds.go b/lib/srv/db/cloud/watchers/rds.go index f00ae8b554a..8626ba88264 100644 --- a/lib/srv/db/cloud/watchers/rds.go +++ b/lib/srv/db/cloud/watchers/rds.go @@ -142,6 +142,14 @@ func (f *rdsFetcher) getAuroraDatabases(ctx context.Context) (types.Databases, e } databases := make(types.Databases, 0, len(clusters)) for _, cluster := range clusters { + if !services.IsRDSClusterSupported(cluster) { + f.log.Debugf("Aurora cluster %q (engine mode %v, engine version %v) doesn't support IAM authentication. Skipping.", + aws.StringValue(cluster.DBClusterIdentifier), + aws.StringValue(cluster.EngineMode), + aws.StringValue(cluster.EngineVersion)) + continue + } + database, err := services.NewDatabaseFromRDSCluster(cluster) if err != nil { f.log.Infof("Could not convert RDS cluster %q to database resource: %v.", diff --git a/lib/srv/db/cloud/watchers/watcher_test.go b/lib/srv/db/cloud/watchers/watcher_test.go index 2a8b46dc779..b8dd1f5e8db 100644 --- a/lib/srv/db/cloud/watchers/watcher_test.go +++ b/lib/srv/db/cloud/watchers/watcher_test.go @@ -43,9 +43,10 @@ func TestWatcher(t *testing.T) { rdsInstance2, _ := makeRDSInstance(t, "instance-2", "us-east-2", map[string]string{"env": "prod"}) rdsInstance3, _ := makeRDSInstance(t, "instance-3", "us-east-1", map[string]string{"env": "dev"}) - auroraCluster1, auroraDatabase1 := makeRDSCluster(t, "cluster-1", "us-east-1", map[string]string{"env": "prod"}) - auroraCluster2, auroraDatabase2 := makeRDSCluster(t, "cluster-2", "us-east-2", map[string]string{"env": "dev"}) - auroraCluster3, _ := makeRDSCluster(t, "cluster-3", "us-east-2", map[string]string{"env": "prod"}) + auroraCluster1, auroraDatabase1 := makeRDSCluster(t, "cluster-1", "us-east-1", services.RDSEngineModeProvisioned, map[string]string{"env": "prod"}) + auroraCluster2, auroraDatabase2 := makeRDSCluster(t, "cluster-2", "us-east-2", services.RDSEngineModeProvisioned, map[string]string{"env": "dev"}) + auroraCluster3, _ := makeRDSCluster(t, "cluster-3", "us-east-2", services.RDSEngineModeProvisioned, map[string]string{"env": "prod"}) + auroraClusterUnsupported, _ := makeRDSCluster(t, "serverless", "us-east-1", services.RDSEngineModeServerless, map[string]string{"env": "prod"}) watcher, err := NewWatcher(ctx, WatcherConfig{ AWSMatchers: []services.AWSMatcher{ @@ -64,7 +65,7 @@ func TestWatcher(t *testing.T) { RDSPerRegion: map[string]rdsiface.RDSAPI{ "us-east-1": &cloud.RDSMock{ DBInstances: []*rds.DBInstance{rdsInstance1, rdsInstance3}, - DBClusters: []*rds.DBCluster{auroraCluster1}, + DBClusters: []*rds.DBCluster{auroraCluster1, auroraClusterUnsupported}, }, "us-east-2": &cloud.RDSMock{ DBInstances: []*rds.DBInstance{rdsInstance2}, @@ -102,12 +103,13 @@ func makeRDSInstance(t *testing.T, name, region string, labels map[string]string return instance, database } -func makeRDSCluster(t *testing.T, name, region string, labels map[string]string) (*rds.DBCluster, types.Database) { +func makeRDSCluster(t *testing.T, name, region, engineMode string, labels map[string]string) (*rds.DBCluster, types.Database) { cluster := &rds.DBCluster{ DBClusterArn: aws.String(fmt.Sprintf("arn:aws:rds:%v:1234567890:cluster:%v", region, name)), DBClusterIdentifier: aws.String(name), DbClusterResourceId: aws.String(uuid.New()), Engine: aws.String(services.RDSEngineAuroraMySQL), + EngineMode: aws.String(engineMode), Endpoint: aws.String("localhost"), Port: aws.Int64(3306), TagList: labelsToTags(labels),