Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 16 additions & 22 deletions lib/cloud/aws/policy_statements.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,15 +175,11 @@ type ExternalCloudAuditPolicyConfig struct {
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
// S3ARNs is a list of all S3 resource ARNs used for audit events, session
// recordings, and Athena query results. For each location, it should include an ARN for the
// base bucket and another wildcard ARN for all objects within the bucket
// and an optional path/prefix.
S3ARNs []string
// AthenaWorkgroupName is the name of the Athena workgroup used for queries.
AthenaWorkgroupName string
// GlueDatabaseName is the name of the AWS Glue database.
Expand All @@ -202,14 +198,8 @@ func (c *ExternalCloudAuditPolicyConfig) CheckAndSetDefaults() error {
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.S3ARNs) < 2 {
return trace.BadParameter("at least two distinct S3 ARNs are required")
}
if len(c.AthenaWorkgroupName) == 0 {
return trace.BadParameter("athena workgroup name is required")
Expand Down Expand Up @@ -241,12 +231,16 @@ func PolicyDocumentForExternalCloudAudit(cfg *ExternalCloudAuditPolicyConfig) (*
"s3:GetObjectVersion",
"s3:ListMultipartUploadParts",
"s3:AbortMultipartUpload",
"s3:ListBucket",
"s3:ListBucketVersions",
"s3:ListBucketMultipartUploads",
"s3:GetBucketOwnershipControls",
"s3:GetBucketPublicAccessBlock",
"s3:GetBucketObjectLockConfiguration",
"s3:GetBucketVersioning",
"s3:GetBucketLocation",
},
Resources: []string{
cfg.AuditEventsARN,
cfg.SessionRecordingsARN,
cfg.AthenaResultsARN,
},
Resources: cfg.S3ARNs,
},
&Statement{
StatementID: "AllowAthenaQuery",
Expand Down
36 changes: 24 additions & 12 deletions lib/integrations/awsoidc/externalcloudaudit_iam_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/gravitational/trace"

"github.com/gravitational/teleport/api/utils"
awslib "github.com/gravitational/teleport/lib/cloud/aws"
"github.com/gravitational/teleport/lib/config"
)
Expand Down Expand Up @@ -72,18 +73,22 @@ func ConfigureExternalCloudAudit(
}

var err error
policyCfg.AuditEventsARN, err = s3URIToObjectWildcardARN(params.Partition, params.AuditEventsURI)
bucketARN, wildcardARN, err := s3URIToResourceARNs(params.Partition, params.AuditEventsURI)
if err != nil {
return trace.Wrap(err, "parsing audit events URI")
}
policyCfg.SessionRecordingsARN, err = s3URIToObjectWildcardARN(params.Partition, params.SessionRecordingsURI)
policyCfg.S3ARNs = append(policyCfg.S3ARNs, bucketARN, wildcardARN)
bucketARN, wildcardARN, err = s3URIToResourceARNs(params.Partition, params.SessionRecordingsURI)
if err != nil {
return trace.Wrap(err, "parsing session recordings URI")
}
policyCfg.AthenaResultsARN, err = s3URIToObjectWildcardARN(params.Partition, params.AthenaResultsURI)
policyCfg.S3ARNs = append(policyCfg.S3ARNs, bucketARN, wildcardARN)
bucketARN, wildcardARN, err = s3URIToResourceARNs(params.Partition, params.AthenaResultsURI)
if err != nil {
return trace.Wrap(err, "parsing athena results URI")
}
policyCfg.S3ARNs = append(policyCfg.S3ARNs, bucketARN, wildcardARN)
policyCfg.S3ARNs = utils.Deduplicate(policyCfg.S3ARNs)

stsResp, err := clt.GetCallerIdentity(ctx, nil)
if err != nil {
Expand Down Expand Up @@ -116,31 +121,38 @@ func ConfigureExternalCloudAudit(
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) {
// s3URIToResourceARNs takes a URI for an s3 bucket with an optional path
// prefix, and returns two AWS s3 resource ARNS. The first is the ARN of the
// bucket, the second is a wildcard ARN matching all objects within the bucket
// and prefix.
// E.g. s3://bucketname/folder -> arn:aws:s3:::bucketname, arn:aws:s3:::bucketname/folder/*
func s3URIToResourceARNs(partition, uri string) (string, string, error) {
u, err := url.Parse(uri)
if err != nil {
return "", trace.BadParameter("parsing S3 URI: %v", err)
return "", "", trace.BadParameter("parsing S3 URI: %v", err)
}

if u.Scheme != "s3" {
return "", trace.BadParameter("URI scheme must be s3")
return "", "", trace.BadParameter("URI scheme must be s3")
}

bucket := u.Host
bucketARN := arn.ARN{
Partition: partition,
Service: "s3",
Resource: bucket,
}

resourcePath := bucket
if folder := strings.Trim(u.Path, "/"); len(folder) > 0 {
resourcePath += "/" + folder
}
resourcePath += "/*"
arn := arn.ARN{
wildcardARN := arn.ARN{
Partition: partition,
Service: "s3",
Resource: resourcePath,
}
return arn.String(), nil

return bucketARN.String(), wildcardARN.String(), nil
}
29 changes: 26 additions & 3 deletions lib/integrations/awsoidc/externalcloudaudit_iam_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"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/google/go-cmp/cmp"
"github.com/gravitational/trace"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -74,11 +75,22 @@ func TestConfigureExternalCloudAudit(t *testing.T) {
"s3:GetObject",
"s3:GetObjectVersion",
"s3:ListMultipartUploadParts",
"s3:AbortMultipartUpload"
"s3:AbortMultipartUpload",
"s3:ListBucket",
"s3:ListBucketVersions",
"s3:ListBucketMultipartUploads",
"s3:GetBucketOwnershipControls",
"s3:GetBucketPublicAccessBlock",
"s3:GetBucketObjectLockConfiguration",
"s3:GetBucketVersioning",
"s3:GetBucketLocation"
],
"Resource": [
"arn:aws:s3:::testbucket_noprefix",
"arn:aws:s3:::testbucket_noprefix/*",
"arn:aws:s3:::testbucket",
"arn:aws:s3:::testbucket/prefix/*",
"arn:aws:s3:::transientbucket",
"arn:aws:s3:::transientbucket/results/*"
],
"Sid": "ReadWriteSessionsAndEvents"
Expand Down Expand Up @@ -143,11 +155,22 @@ func TestConfigureExternalCloudAudit(t *testing.T) {
"s3:GetObject",
"s3:GetObjectVersion",
"s3:ListMultipartUploadParts",
"s3:AbortMultipartUpload"
"s3:AbortMultipartUpload",
"s3:ListBucket",
"s3:ListBucketVersions",
"s3:ListBucketMultipartUploads",
"s3:GetBucketOwnershipControls",
"s3:GetBucketPublicAccessBlock",
"s3:GetBucketObjectLockConfiguration",
"s3:GetBucketVersioning",
"s3:GetBucketLocation"
],
"Resource": [
"arn:aws-cn:s3:::testbucket_noprefix",
"arn:aws-cn:s3:::testbucket_noprefix/*",
"arn:aws-cn:s3:::testbucket",
"arn:aws-cn:s3:::testbucket/prefix/*",
"arn:aws-cn:s3:::transientbucket",
"arn:aws-cn:s3:::transientbucket/results/*"
],
"Sid": "ReadWriteSessionsAndEvents"
Expand Down Expand Up @@ -237,7 +260,7 @@ func TestConfigureExternalCloudAudit(t *testing.T) {
return
}
require.NoError(t, err, trace.DebugReport(err))
require.Equal(t, tc.expectedRolePolicies, currentRolePolicies)
require.Equal(t, tc.expectedRolePolicies, currentRolePolicies, cmp.Diff(tc.expectedRolePolicies["test-role"]["test-policy"], currentRolePolicies["test-role"]["test-policy"]))
})
}
}
Expand Down