diff --git a/lib/cloud/aws/policy.go b/lib/cloud/aws/policy.go index 40fbbe62a56e3..d0dd5abd5c80c 100644 --- a/lib/cloud/aws/policy.go +++ b/lib/cloud/aws/policy.go @@ -82,6 +82,8 @@ type Statement struct { // StringEquals: // "proxy.example.com:aud": "discover.teleport" Conditions map[string]map[string]SliceOrString `json:"Condition,omitempty"` + // StatementID is an optional identifier for the statement. + StatementID string `json:"Sid,omitempty"` } // ensureResource ensures that the statement contains the specified resource. diff --git a/lib/cloud/aws/policy_statements.go b/lib/cloud/aws/policy_statements.go index 25c7bd71848bd..14d6f43177644 100644 --- a/lib/cloud/aws/policy_statements.go +++ b/lib/cloud/aws/policy_statements.go @@ -16,7 +16,12 @@ limitations under the License. package aws -import "fmt" +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/gravitational/trace" +) var ( allResources = []string{"*"} @@ -159,3 +164,140 @@ func StatementForListRDSDatabases() *Statement { Resources: allResources, } } + +// ExternalCloudAuditPolicyConfig holds options for the external cloud audit +// IAM policy. +type ExternalCloudAuditPolicyConfig struct { + // Partition is the AWS partition to use. + Partition string + // Region is the AWS region to use. + Region string + // Account is the AWS account ID to use. + Account string + // AuditEventsARN is the S3 resource ARN where audit events are stored, + // including the bucket name, (optional) prefix, and a trailing wildcard + AuditEventsARN string + // SessionRecordingsARN is the S3 resource ARN where session recordings are stored, + // including the bucket name, (optional) prefix, and a trailing wildcard + SessionRecordingsARN string + // AthenaResultsARN is the S3 resource ARN where athena results are stored, + // including the bucket name, (optional) prefix, and a trailing wildcard + AthenaResultsARN string + // AthenaWorkgroupName is the name of the Athena workgroup used for queries. + AthenaWorkgroupName string + // GlueDatabaseName is the name of the AWS Glue database. + GlueDatabaseName string + // GlueTabelName is the name of the AWS Glue table. + GlueTableName string +} + +func (c *ExternalCloudAuditPolicyConfig) CheckAndSetDefaults() error { + if len(c.Partition) == 0 { + c.Partition = "aws" + } + if len(c.Region) == 0 { + return trace.BadParameter("region is required") + } + if len(c.Account) == 0 { + return trace.BadParameter("account is required") + } + if len(c.AuditEventsARN) == 0 { + return trace.BadParameter("audit events ARN is required") + } + if len(c.SessionRecordingsARN) == 0 { + return trace.BadParameter("session recordings ARN is required") + } + if len(c.AthenaResultsARN) == 0 { + return trace.BadParameter("athena results ARN is required") + } + if len(c.AthenaWorkgroupName) == 0 { + return trace.BadParameter("athena workgroup name is required") + } + if len(c.GlueDatabaseName) == 0 { + return trace.BadParameter("glue database name is required") + } + if len(c.GlueTableName) == 0 { + return trace.BadParameter("glue table name is required") + } + return nil +} + +// PolicyDocumentForExternalCloudAudit returns a PolicyDocument with the +// necessary IAM permissions for the External Cloud Audit feature. +func PolicyDocumentForExternalCloudAudit(cfg *ExternalCloudAuditPolicyConfig) (*PolicyDocument, error) { + if err := cfg.CheckAndSetDefaults(); err != nil { + return nil, trace.Wrap(err) + } + return &PolicyDocument{ + Version: PolicyVersion, + Statements: []*Statement{ + &Statement{ + StatementID: "ReadWriteSessionsAndEvents", + Effect: EffectAllow, + Actions: []string{ + "s3:PutObject", + "s3:GetObject", + "s3:GetObjectVersion", + "s3:ListMultipartUploadParts", + "s3:AbortMultipartUpload", + }, + Resources: []string{ + cfg.AuditEventsARN, + cfg.SessionRecordingsARN, + cfg.AthenaResultsARN, + }, + }, + &Statement{ + StatementID: "AllowAthenaQuery", + Effect: EffectAllow, + Actions: []string{ + "athena:StartQueryExecution", + "athena:GetQueryResults", + "athena:GetQueryExecution", + }, + Resources: []string{ + arn.ARN{ + Partition: cfg.Partition, + Service: "athena", + Region: cfg.Region, + AccountID: cfg.Account, + Resource: "workgroup/" + cfg.AthenaWorkgroupName, + }.String(), + }, + }, + &Statement{ + StatementID: "FullAccessOnGlueTable", + Effect: EffectAllow, + Actions: []string{ + "glue:GetTable", + "glue:GetTableVersion", + "glue:GetTableVersions", + "glue:UpdateTable", + }, + Resources: []string{ + arn.ARN{ + Partition: cfg.Partition, + Service: "glue", + Region: cfg.Region, + AccountID: cfg.Account, + Resource: "catalog", + }.String(), + arn.ARN{ + Partition: cfg.Partition, + Service: "glue", + Region: cfg.Region, + AccountID: cfg.Account, + Resource: "database/" + cfg.GlueDatabaseName, + }.String(), + arn.ARN{ + Partition: cfg.Partition, + Service: "glue", + Region: cfg.Region, + AccountID: cfg.Account, + Resource: "table/" + cfg.GlueDatabaseName + "/" + cfg.GlueTableName, + }.String(), + }, + }, + }, + }, nil +} diff --git a/lib/config/configuration.go b/lib/config/configuration.go index 2cada195c831a..a2442a2d408cd 100644 --- a/lib/config/configuration.go +++ b/lib/config/configuration.go @@ -212,6 +212,10 @@ type CommandLineFlags struct { // IntegrationConfListDatabasesIAMArguments contains the arguments of // `teleport integration configure listdatabases-iam` command IntegrationConfListDatabasesIAMArguments IntegrationConfListDatabasesIAM + + // IntegrationConfExternalCloudAuditIAMArguments contains the arguments of the + // `teleport integration configure externalcloudaudit-iam` command + IntegrationConfExternalCloudAuditIAMArguments IntegrationConfExternalCloudAuditIAM } // IntegrationConfDeployServiceIAM contains the arguments of @@ -263,6 +267,31 @@ type IntegrationConfListDatabasesIAM struct { Role string } +// IntegrationConfExternalCloudAuditIAM contains the arguments of the +// `teleport integration configure externalcloudaudit-iam` command +type IntegrationConfExternalCloudAuditIAM struct { + // Region is the AWS Region used. + Region string + // Role is the AWS IAM Role associated with the OIDC integration. + Role string + // Policy is the name to use for the IAM policy. + Policy string + // SessionRecordingsURI is the S3 URI where session recordings are stored. + SessionRecordingsURI string + // AuditEventsURI is the S3 URI where audit events are stored. + AuditEventsURI string + // AthenaResultsURI is the S3 URI where temporary Athena results are stored. + AthenaResultsURI string + // AthenaWorkgroup is the name of the Athena workgroup used. + AthenaWorkgroup string + // GlueDatabase is the name of the Glue database used. + GlueDatabase string + // GlueTable is the name of the Glue table used. + GlueTable string + // Partition is the AWS partition to use (optional). + Partition string +} + // ReadConfigFile reads /etc/teleport.yaml (or whatever is passed via --config flag) // and overrides values in 'cfg' structure func ReadConfigFile(cliConfigPath string) (*FileConfig, error) { diff --git a/lib/integrations/awsoidc/externalcloudaudit_iam_config.go b/lib/integrations/awsoidc/externalcloudaudit_iam_config.go new file mode 100644 index 0000000000000..649ce33f0eb37 --- /dev/null +++ b/lib/integrations/awsoidc/externalcloudaudit_iam_config.go @@ -0,0 +1,146 @@ +// Copyright 2023 Gravitational, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package awsoidc + +import ( + "context" + "net/url" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/aws/aws-sdk-go-v2/service/iam" + "github.com/aws/aws-sdk-go-v2/service/sts" + "github.com/gravitational/trace" + + awslib "github.com/gravitational/teleport/lib/cloud/aws" + "github.com/gravitational/teleport/lib/config" +) + +// ConfigureExternalCloudAuditClient is an interface for the AWS client methods +// used by ConfigureExternalCloudAudit. +type ConfigureExternalCloudAuditClient interface { + PutRolePolicy(context.Context, *iam.PutRolePolicyInput, ...func(*iam.Options)) (*iam.PutRolePolicyOutput, error) + GetCallerIdentity(context.Context, *sts.GetCallerIdentityInput, ...func(*sts.Options)) (*sts.GetCallerIdentityOutput, error) +} + +// DefaultConfigureExternalCloudAuditClient wraps an iam and sts client to +// implement ConfigureExternalCloudAuditClient. +type DefaultConfigureExternalCloudAuditClient struct { + Iam *iam.Client + Sts *sts.Client +} + +// PutRolePolicy adds or updates an inline policy document that is embedded in +// the specified IAM role. +func (d *DefaultConfigureExternalCloudAuditClient) PutRolePolicy(ctx context.Context, input *iam.PutRolePolicyInput, opts ...func(*iam.Options)) (*iam.PutRolePolicyOutput, error) { + return d.Iam.PutRolePolicy(ctx, input, opts...) +} + +// GetCallerIdentity returns details about the IAM user or role whose +// credentials are used to call the operation. +func (d *DefaultConfigureExternalCloudAuditClient) GetCallerIdentity(ctx context.Context, input *sts.GetCallerIdentityInput, opts ...func(*sts.Options)) (*sts.GetCallerIdentityOutput, error) { + return d.Sts.GetCallerIdentity(ctx, input, opts...) +} + +// ConfigureExternalCloudAudit attaches an IAM policy with necessary permissions +// for the ExternalCloudAudit feature to an existing IAM role associated with an +// AWS OIDC integration. +func ConfigureExternalCloudAudit( + ctx context.Context, + clt ConfigureExternalCloudAuditClient, + params *config.IntegrationConfExternalCloudAuditIAM, +) error { + policyCfg := &awslib.ExternalCloudAuditPolicyConfig{ + Partition: params.Partition, + Region: params.Region, + AthenaWorkgroupName: params.AthenaWorkgroup, + GlueDatabaseName: params.GlueDatabase, + GlueTableName: params.GlueTable, + } + + var err error + policyCfg.AuditEventsARN, err = s3URIToObjectWildcardARN(params.Partition, params.AuditEventsURI) + if err != nil { + return trace.Wrap(err, "parsing audit events URI") + } + policyCfg.SessionRecordingsARN, err = s3URIToObjectWildcardARN(params.Partition, params.SessionRecordingsURI) + if err != nil { + return trace.Wrap(err, "parsing session recordings URI") + } + policyCfg.AthenaResultsARN, err = s3URIToObjectWildcardARN(params.Partition, params.AthenaResultsURI) + if err != nil { + return trace.Wrap(err, "parsing athena results URI") + } + + stsResp, err := clt.GetCallerIdentity(ctx, nil) + if err != nil { + return trace.Wrap(err, "attempting to find caller's AWS account ID: call to sts:GetCallerIdentity failed") + } + policyCfg.Account = aws.ToString(stsResp.Account) + + policyDoc, err := awslib.PolicyDocumentForExternalCloudAudit(policyCfg) + if err != nil { + return trace.Wrap(err) + } + policyDocString, err := policyDoc.Marshal() + if err != nil { + return trace.Wrap(err) + } + + _, err = clt.PutRolePolicy(ctx, &iam.PutRolePolicyInput{ + PolicyName: ¶ms.Policy, + RoleName: ¶ms.Role, + PolicyDocument: &policyDocString, + }) + if err != nil { + err = awslib.ConvertIAMv2Error(err) + if trace.IsNotFound(err) { + return trace.NotFound("role %q not found", params.Role) + } + return trace.Wrap(err) + } + + return nil +} + +// s3URIToObjectWildcardARN takes a URI for an s3 bucket with an optional path +// prefix (folder) and returns a wildcard ARN to match all objects in that +// bucket (within the prefix). +// E.g. s3://bucketname/folder -> arn:aws:s3:::bucketname/folder/* +func s3URIToObjectWildcardARN(partition, uri string) (string, error) { + u, err := url.Parse(uri) + if err != nil { + return "", trace.BadParameter("parsing S3 URI: %v", err) + } + + if u.Scheme != "s3" { + return "", trace.BadParameter("URI scheme must be s3") + } + + bucket := u.Host + + resourcePath := bucket + if folder := strings.Trim(u.Path, "/"); len(folder) > 0 { + resourcePath += "/" + folder + } + resourcePath += "/*" + arn := arn.ARN{ + Partition: partition, + Service: "s3", + Resource: resourcePath, + } + return arn.String(), nil +} diff --git a/lib/integrations/awsoidc/externalcloudaudit_iam_config_test.go b/lib/integrations/awsoidc/externalcloudaudit_iam_config_test.go new file mode 100644 index 0000000000000..ff8eb0d53fc99 --- /dev/null +++ b/lib/integrations/awsoidc/externalcloudaudit_iam_config_test.go @@ -0,0 +1,284 @@ +// Copyright 2023 Gravitational, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package awsoidc + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/iam" + iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types" + "github.com/aws/aws-sdk-go-v2/service/sts" + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/lib/config" +) + +// TestConfigureExternalCloudAudit tests that ConfigureExternalCloudAudit +// creates a well-formatted IAM policy and attaches it to the correct role, and +// behaves well in error cases. +func TestConfigureExternalCloudAudit(t *testing.T) { + ctx := context.Background() + + for _, tc := range []struct { + desc string + params *config.IntegrationConfExternalCloudAuditIAM + stsAccount string + existingRolePolicies map[string]map[string]string + expectedRolePolicies map[string]map[string]string + errorContains []string + }{ + { + // A passing case with the account from sts:GetCallerIdentity + desc: "passing", + params: &config.IntegrationConfExternalCloudAuditIAM{ + Partition: "aws", + Region: "us-west-2", + Role: "test-role", + Policy: "test-policy", + AuditEventsURI: "s3://testbucket_noprefix", + SessionRecordingsURI: "s3://testbucket/prefix", + AthenaResultsURI: "s3://transientbucket/results", + AthenaWorkgroup: "testworkgroup", + GlueDatabase: "testdb", + GlueTable: "testtable", + }, + stsAccount: "12345678", + existingRolePolicies: map[string]map[string]string{ + "test-role": {}, + }, + expectedRolePolicies: map[string]map[string]string{ + "test-role": { + "test-policy": `{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:GetObjectVersion", + "s3:ListMultipartUploadParts", + "s3:AbortMultipartUpload" + ], + "Resource": [ + "arn:aws:s3:::testbucket_noprefix/*", + "arn:aws:s3:::testbucket/prefix/*", + "arn:aws:s3:::transientbucket/results/*" + ], + "Sid": "ReadWriteSessionsAndEvents" + }, + { + "Effect": "Allow", + "Action": [ + "athena:StartQueryExecution", + "athena:GetQueryResults", + "athena:GetQueryExecution" + ], + "Resource": "arn:aws:athena:us-west-2:12345678:workgroup/testworkgroup", + "Sid": "AllowAthenaQuery" + }, + { + "Effect": "Allow", + "Action": [ + "glue:GetTable", + "glue:GetTableVersion", + "glue:GetTableVersions", + "glue:UpdateTable" + ], + "Resource": [ + "arn:aws:glue:us-west-2:12345678:catalog", + "arn:aws:glue:us-west-2:12345678:database/testdb", + "arn:aws:glue:us-west-2:12345678:table/testdb/testtable" + ], + "Sid": "FullAccessOnGlueTable" + } + ] +}`, + }, + }, + }, + { + desc: "alternate partition and region", + params: &config.IntegrationConfExternalCloudAuditIAM{ + Partition: "aws-cn", + Region: "cn-north-1", + Role: "test-role", + Policy: "test-policy", + AuditEventsURI: "s3://testbucket_noprefix", + SessionRecordingsURI: "s3://testbucket/prefix", + AthenaResultsURI: "s3://transientbucket/results", + AthenaWorkgroup: "testworkgroup", + GlueDatabase: "testdb", + GlueTable: "testtable", + }, + stsAccount: "12345678", + existingRolePolicies: map[string]map[string]string{ + "test-role": {}, + }, + expectedRolePolicies: map[string]map[string]string{ + "test-role": { + "test-policy": `{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:GetObjectVersion", + "s3:ListMultipartUploadParts", + "s3:AbortMultipartUpload" + ], + "Resource": [ + "arn:aws-cn:s3:::testbucket_noprefix/*", + "arn:aws-cn:s3:::testbucket/prefix/*", + "arn:aws-cn:s3:::transientbucket/results/*" + ], + "Sid": "ReadWriteSessionsAndEvents" + }, + { + "Effect": "Allow", + "Action": [ + "athena:StartQueryExecution", + "athena:GetQueryResults", + "athena:GetQueryExecution" + ], + "Resource": "arn:aws-cn:athena:cn-north-1:12345678:workgroup/testworkgroup", + "Sid": "AllowAthenaQuery" + }, + { + "Effect": "Allow", + "Action": [ + "glue:GetTable", + "glue:GetTableVersion", + "glue:GetTableVersions", + "glue:UpdateTable" + ], + "Resource": [ + "arn:aws-cn:glue:cn-north-1:12345678:catalog", + "arn:aws-cn:glue:cn-north-1:12345678:database/testdb", + "arn:aws-cn:glue:cn-north-1:12345678:table/testdb/testtable" + ], + "Sid": "FullAccessOnGlueTable" + } + ] +}`, + }, + }, + }, + { + desc: "bad uri", + params: &config.IntegrationConfExternalCloudAuditIAM{ + Partition: "aws", + Region: "us-west-2", + Role: "test-role", + SessionRecordingsURI: "file:///tmp/recordings", + AuditEventsURI: "s3://longtermbucket/events", + AthenaResultsURI: "s3://transientbucket/results", + AthenaWorkgroup: "testworkgroup", + GlueDatabase: "testdb", + GlueTable: "testtable", + }, + stsAccount: "12345678", + existingRolePolicies: map[string]map[string]string{ + "test-role": {}, + }, + errorContains: []string{ + "parsing session recordings URI", + "URI scheme must be s3", + }, + }, + { + desc: "role not found", + params: &config.IntegrationConfExternalCloudAuditIAM{ + Partition: "aws", + Region: "us-west-2", + Role: "bad-role", + SessionRecordingsURI: "s3://longtermbucket/recordings", + AuditEventsURI: "s3://longtermbucket/events", + AthenaResultsURI: "s3://transientbucket/results", + AthenaWorkgroup: "testworkgroup", + GlueDatabase: "testdb", + GlueTable: "testtable", + }, + stsAccount: "12345678", + errorContains: []string{ + `role "bad-role" not found`, + }, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + currentRolePolicies := cloneRolePolicies(tc.existingRolePolicies) + clt := &fakeConfigureExternalCloudAuditClient{ + account: tc.stsAccount, + rolePolicies: currentRolePolicies, + } + err := ConfigureExternalCloudAudit(ctx, clt, tc.params) + if len(tc.errorContains) > 0 { + for _, msg := range tc.errorContains { + require.ErrorContains(t, err, msg) + } + return + } + require.NoError(t, err, trace.DebugReport(err)) + require.Equal(t, tc.expectedRolePolicies, currentRolePolicies) + }) + } +} + +type fakeConfigureExternalCloudAuditClient struct { + account string + // rolePolicies is a nested map holding the state of existing roles and + // their attached policies. Each outer key is a role name, the value is a + // map of policy names to policy documents. + rolePolicies map[string]map[string]string +} + +func (f *fakeConfigureExternalCloudAuditClient) PutRolePolicy(ctx context.Context, input *iam.PutRolePolicyInput, opts ...func(*iam.Options)) (*iam.PutRolePolicyOutput, error) { + roleName := aws.ToString(input.RoleName) + if _, roleExists := f.rolePolicies[roleName]; !roleExists { + return nil, &iamTypes.NoSuchEntityException{ + Message: aws.String(fmt.Sprintf("role %q does not exist", roleName)), + } + } + if f.rolePolicies[roleName] == nil { + f.rolePolicies[roleName] = make(map[string]string) + } + f.rolePolicies[roleName][aws.ToString(input.PolicyName)] = aws.ToString(input.PolicyDocument) + return &iam.PutRolePolicyOutput{}, nil +} + +func (f *fakeConfigureExternalCloudAuditClient) GetCallerIdentity(ctx context.Context, input *sts.GetCallerIdentityInput, opts ...func(*sts.Options)) (*sts.GetCallerIdentityOutput, error) { + return &sts.GetCallerIdentityOutput{ + Account: aws.String(f.account), + Arn: aws.String("some_ignored_arn"), + UserId: aws.String("some_ignored_user_id"), + }, nil +} + +func cloneRolePolicies(in map[string]map[string]string) map[string]map[string]string { + out := make(map[string]map[string]string, len(in)) + for role, policies := range in { + out[role] = make(map[string]string, len(policies)) + for policyName, policyDoc := range policies { + out[role][policyName] = policyDoc + } + } + return out +} diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index 6e183bc336ddf..3a8712c92555c 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -31,6 +31,7 @@ import ( "github.com/alecthomas/kingpin/v2" awsConfig "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/iam" + "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/gravitational/trace" log "github.com/sirupsen/logrus" @@ -473,6 +474,18 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con integrationConfListDatabasesCmd.Flag("aws-region", "AWS Region.").Required().StringVar(&ccf.IntegrationConfListDatabasesIAMArguments.Region) integrationConfListDatabasesCmd.Flag("role", "The AWS Role used by the AWS OIDC Integration.").Required().StringVar(&ccf.IntegrationConfListDatabasesIAMArguments.Role) + integrationConfExternalAuditCmd := integrationConfigureCmd.Command("externalcloudaudit-iam", "Adds required IAM permissions for external cloud audit logs") + integrationConfExternalAuditCmd.Flag("aws-region", "AWS region.").Required().StringVar(&ccf.IntegrationConfExternalCloudAuditIAMArguments.Region) + integrationConfExternalAuditCmd.Flag("role", "The IAM Role used by the AWS OIDC Integration.").Required().StringVar(&ccf.IntegrationConfExternalCloudAuditIAMArguments.Role) + integrationConfExternalAuditCmd.Flag("policy", "The name for the Policy to attach to the IAM role.").Required().StringVar(&ccf.IntegrationConfExternalCloudAuditIAMArguments.Policy) + integrationConfExternalAuditCmd.Flag("session-recordings", "The S3 URI where session recordings are stored.").Required().StringVar(&ccf.IntegrationConfExternalCloudAuditIAMArguments.SessionRecordingsURI) + integrationConfExternalAuditCmd.Flag("audit-events", "The S3 URI where audit events are stored.").Required().StringVar(&ccf.IntegrationConfExternalCloudAuditIAMArguments.AuditEventsURI) + integrationConfExternalAuditCmd.Flag("athena-results", "The S3 URI where athena results are stored.").Required().StringVar(&ccf.IntegrationConfExternalCloudAuditIAMArguments.AthenaResultsURI) + integrationConfExternalAuditCmd.Flag("athena-workgroup", "The name of the Athena workgroup used.").Required().StringVar(&ccf.IntegrationConfExternalCloudAuditIAMArguments.AthenaWorkgroup) + integrationConfExternalAuditCmd.Flag("glue-database", "The name of the Glue database used.").Required().StringVar(&ccf.IntegrationConfExternalCloudAuditIAMArguments.GlueDatabase) + integrationConfExternalAuditCmd.Flag("glue-table", "The name of the Glue table used.").Required().StringVar(&ccf.IntegrationConfExternalCloudAuditIAMArguments.GlueTable) + integrationConfExternalAuditCmd.Flag("aws-partition", "AWS partition (default: aws).").Default("aws").StringVar(&ccf.IntegrationConfExternalCloudAuditIAMArguments.Partition) + // parse CLI commands+flags: utils.UpdateAppUsageTemplate(app, options.Args) command, err := app.Parse(options.Args) @@ -566,6 +579,8 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con err = onIntegrationConfAWSOIDCIdP(ccf.IntegrationConfAWSOIDCIdPArguments) case integrationConfListDatabasesCmd.FullCommand(): err = onIntegrationConfListDatabasesIAM(ccf.IntegrationConfListDatabasesIAMArguments) + case integrationConfExternalAuditCmd.FullCommand(): + err = onIntegrationConfExternalAuditCmd(ccf.IntegrationConfExternalCloudAuditIAMArguments) } if err != nil { utils.FatalError(err) @@ -991,3 +1006,16 @@ func onIntegrationConfListDatabasesIAM(params config.IntegrationConfListDatabase return nil } + +func onIntegrationConfExternalAuditCmd(params config.IntegrationConfExternalCloudAuditIAM) error { + ctx := context.Background() + cfg, err := awsConfig.LoadDefaultConfig(ctx, awsConfig.WithRegion(params.Region)) + if err != nil { + return trace.Wrap(err) + } + clt := &awsoidc.DefaultConfigureExternalCloudAuditClient{ + Iam: iam.NewFromConfig(cfg), + Sts: sts.NewFromConfig(cfg), + } + return trace.Wrap(awsoidc.ConfigureExternalCloudAudit(ctx, clt, ¶ms)) +}