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
12 changes: 12 additions & 0 deletions lib/cloud/aws/policy_statements.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,18 @@ func StatementKMSDecrypt(kmsKeysARNs []string) *Statement {
}
}

// StatementEnableEKSAuditLogs returns the statement that allows fetching EKS
// API server audit logs from CloudWatch Logs.
func StatementAccessGraphAWSSyncEKSAuditLogs() *Statement {
return &Statement{
Effect: EffectAllow,
Actions: []string{
"logs:FilterLogEvents",
},
Resources: []string{"arn:aws:logs:*:*:log-group:/aws/eks/*"},
}
}

// StatementForAWSIdentityCenterAccess returns AWS IAM policy statement that grants
// permissions required for Teleport identity center client.
// TODO(sshah): make the roles more granular by restricting resources scoped to
Expand Down
2 changes: 2 additions & 0 deletions lib/config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,8 @@ type IntegrationConfAccessGraphAWSSync struct {
CloudTrailBucketARN string
// KMSKeyARNs is the ARN of the KMS key to use for decrypting the Identity Security Activity Center data.
KMSKeyARNs []string
// EnableEKSAuditLogs enables collection of EKS audit logs from CloudWatch logs.
EnableEKSAuditLogs bool
}

// IntegrationConfAccessGraphAzureSync contains the arguments of
Expand Down
6 changes: 6 additions & 0 deletions lib/integrations/awsoidc/accessgraph_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ type AccessGraphAWSIAMConfigureRequest struct {
// KMSKeyARNs is the ARN of the KMS key to use for decrypting the Identity Security Activity Center data.
KMSKeyARNs []string

// EnableEKSAuditLogs enables collection of EKS audit logs from CloudWatch logs.
EnableEKSAuditLogs bool

// stdout is used to override stdout output in tests.
stdout io.Writer
}
Expand Down Expand Up @@ -181,6 +184,9 @@ func ConfigureAccessGraphSyncIAM(ctx context.Context, clt AccessGraphIAMConfigur
statements = append(statements, awslib.StatementKMSDecrypt(req.KMSKeyARNs))
}

if req.EnableEKSAuditLogs {
statements = append(statements, awslib.StatementAccessGraphAWSSyncEKSAuditLogs())
}
policy := awslib.NewPolicyDocument(
statements...,
)
Expand Down
1 change: 1 addition & 0 deletions lib/integrations/awsoidc/accessgraph_sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ func TestAccessGraphAWSIAMConfigWithActivityCenterOuput(t *testing.T) {
SQSQueueURL: "https://sqs.us-west-2.amazonaws.com/123456789012/my-queue",
CloudTrailBucketARN: "arn:aws:s3:::my-cloudtrail-bucket",
KMSKeyARNs: []string{"arn:aws:kms:us-west-2:123456789012:key/my-kms-key"},
EnableEKSAuditLogs: true,
}
clt := mockAccessGraphAWSAMConfigClient{
CallerIdentityGetter: mockSTSClient{accountID: req.AccountID},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ PutRolePolicy: {
"kms:GenerateDataKeyWithoutPlaintext"
],
"Resource": "arn:aws:kms:us-west-2:123456789012:key/my-kms-key"
},
{
"Effect": "Allow",
"Action": "logs:FilterLogEvents",
"Resource": "arn:aws:logs:*:*:log-group:/aws/eks/*"
}
]
},
Expand Down
14 changes: 14 additions & 0 deletions lib/web/integrations_awsoidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"maps"
"net/http"
"slices"
"strconv"
"strings"

"github.com/aws/aws-sdk-go-v2/aws/arn"
Expand Down Expand Up @@ -1380,6 +1381,19 @@ func (h *Handler) awsAccessGraphOIDCSync(w http.ResponseWriter, r *http.Request,
}
}

if eksAuditLogs := queryParams.Get("eksAuditLogs"); eksAuditLogs != "" {
enabled, err := strconv.ParseBool(eksAuditLogs)
if err != nil {
// The error returned by ParseBool contains no more information than this
// error. As we canot wrap both it and trace.BadParameter, we do the
// latter as a preferred error type.
return nil, trace.BadParameter("invalid boolean value for eksAuditLogs %q", eksAuditLogs)
}
if enabled {
argsList = append(argsList, "--eks-audit-logs")
}
}

script, err := oneoff.BuildScript(oneoff.OneOffScriptParams{
EntrypointArgs: strings.Join(argsList, " "),
SuccessMessage: "Success! You can now go back to the Teleport Web UI to complete the Access Graph AWS Sync enrollment.",
Expand Down
164 changes: 162 additions & 2 deletions lib/web/integrations_awsoidc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,13 +308,173 @@ func TestBuildEC2SSMIAMScript(t *testing.T) {
}
}

func TestBuildAccessGraphCloudSyncIAMScript(t *testing.T) {
t.Parallel()
isBadParamErrFn := func(tt require.TestingT, err error, i ...any) {
require.True(tt, trace.IsBadParameter(err), "expected bad parameter, got %v", err)
}

env := newWebPack(t, 1)

// Unauthenticated client for script downloading.
anonymousHTTPClient := env.proxies[0].newClient(t)
pathVars := []string{
"webapi",
"scripts",
"integrations",
"configure",
"access-graph-cloud-sync-iam.sh",
}
endpoint := anonymousHTTPClient.Endpoint(pathVars...)

role := "myRole"
awsAccountID := "123456789012"
sqsUrl := "https://sqs.us-west-2.amazonaws.com/123456789012/queue-name"
cloudTrailS3Bucket := "arn:aws:s3:::bucket-name"
kmsKey1 := "arn:aws:kms:us-west-2:123456789012:key/00000000-1111-2222-3333-444444444444"
kmsKey2 := "arn:aws:kms:us-west-2:123456789012:key/55555555-6666-7777-8888-999999999999"

tests := []struct {
name string
reqRelativeURL string
reqQuery url.Values
errCheck require.ErrorAssertionFunc
expectedTeleportArgs string
}{
{
name: "valid",
reqQuery: url.Values{
"kind": []string{"aws-iam"},
"role": []string{role},
"awsAccountID": []string{awsAccountID},
},
errCheck: require.NoError,
expectedTeleportArgs: "integration configure access-graph aws-iam" +
" --role=" + role +
" --aws-account-id=" + awsAccountID,
},
{
name: "valid with cloud trail",
reqQuery: url.Values{
"kind": []string{"aws-iam"},
"role": []string{role},
"awsAccountID": []string{awsAccountID},
"sqsUrl": []string{sqsUrl},
"cloudTrailS3Bucket": []string{cloudTrailS3Bucket},
"kmsKeysARNs": []string{kmsKey1, kmsKey2},
},
errCheck: require.NoError,
expectedTeleportArgs: "integration configure access-graph aws-iam" +
" --role=" + role +
" --aws-account-id=" + awsAccountID +
" --sqs-queue-url=" + sqsUrl +
" --cloud-trail-bucket=" + cloudTrailS3Bucket +
" --kms-key=" + kmsKey1 +
" --kms-key=" + kmsKey2,
},
{
name: "valid with eks audit logs",
reqQuery: url.Values{
"kind": []string{"aws-iam"},
"role": []string{role},
"awsAccountID": []string{awsAccountID},
"eksAuditLogs": []string{"true"},
},
errCheck: require.NoError,
expectedTeleportArgs: "integration configure access-graph aws-iam" +
" --role=" + role +
" --aws-account-id=" + awsAccountID +
" --eks-audit-logs",
},
{
name: "valid with cloud trail and eks audit logs",
reqQuery: url.Values{
"kind": []string{"aws-iam"},
"role": []string{role},
"awsAccountID": []string{awsAccountID},
"sqsUrl": []string{sqsUrl},
"cloudTrailS3Bucket": []string{cloudTrailS3Bucket},
"kmsKeysARNs": []string{kmsKey1, kmsKey2},
"eksAuditLogs": []string{"true"},
},
errCheck: require.NoError,
expectedTeleportArgs: "integration configure access-graph aws-iam" +
" --role=" + role +
" --aws-account-id=" + awsAccountID +
" --sqs-queue-url=" + sqsUrl +
" --cloud-trail-bucket=" + cloudTrailS3Bucket +
" --kms-key=" + kmsKey1 +
" --kms-key=" + kmsKey2 +
" --eks-audit-logs",
},
{
name: "valid with symbols in role",
reqQuery: url.Values{
"kind": []string{"aws-iam"},
"role": []string{"Test+1=2,3.4@5-6_7"},
"awsAccountID": []string{"123456789012"},
},
errCheck: require.NoError,
expectedTeleportArgs: "integration configure access-graph aws-iam " +
"--role=Test\\+1=2,3.4\\@5-6_7 " +
"--aws-account-id=123456789012",
},
{
name: "missing kind",
reqQuery: url.Values{
"role": []string{"myRole"},
"awsAccountID": []string{"123456789012"},
},
errCheck: isBadParamErrFn,
},
{
name: "missing role",
reqQuery: url.Values{
"kind": []string{"aws-iam"},
"awsAccountID": []string{"123456789012"},
},
errCheck: isBadParamErrFn,
},
{
name: "missing awsAccountID",
reqQuery: url.Values{
"kind": []string{"aws-iam"},
"role": []string{"myRole"},
},
errCheck: isBadParamErrFn,
},
{
name: "trying to inject escape sequence into query params",
reqQuery: url.Values{
"kind": []string{"aws-iam"},
"role": []string{"'; rm -rf /tmp/dir; echo '"},
"awsAccountID": []string{"123456789012"},
},
errCheck: isBadParamErrFn,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
resp, err := anonymousHTTPClient.Get(t.Context(), endpoint, tc.reqQuery)
tc.errCheck(t, err)
if err != nil {
return
}

require.Contains(t, string(resp.Bytes()),
fmt.Sprintf("entrypointArgs='%s'\n", tc.expectedTeleportArgs),
)
})
}
}

func TestBuildAWSAppAccessConfigureIAMScript(t *testing.T) {
t.Parallel()
isBadParamErrFn := func(tt require.TestingT, err error, i ...any) {
require.True(tt, trace.IsBadParameter(err), "expected bad parameter, got %v", err)
}

ctx := context.Background()
env := newWebPack(t, 1)

// Unauthenticated client for script downloading.
Expand Down Expand Up @@ -370,7 +530,7 @@ func TestBuildAWSAppAccessConfigureIAMScript(t *testing.T) {

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
resp, err := anonymousHTTPClient.Get(ctx, endpoint, tc.reqQuery)
resp, err := anonymousHTTPClient.Get(t.Context(), endpoint, tc.reqQuery)
tc.errCheck(t, err)
if err != nil {
return
Expand Down
1 change: 1 addition & 0 deletions tool/teleport/common/integration_configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ func onIntegrationConfAccessGraphAWSSync(ctx context.Context, params config.Inte
SQSQueueURL: params.SQSQueueURL,
CloudTrailBucketARN: params.CloudTrailBucketARN,
KMSKeyARNs: params.KMSKeyARNs,
EnableEKSAuditLogs: params.EnableEKSAuditLogs,
}
return trace.Wrap(awsoidc.ConfigureAccessGraphSyncIAM(ctx, clt, confReq))
}
Expand Down
1 change: 1 addition & 0 deletions tool/teleport/common/teleport.go
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,7 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con
integrationConfAccessGraphAWSSyncCmd.Flag("sqs-queue-url", "SQS Queue URL used to receive notifications from CloudTrail.").StringVar(&ccf.IntegrationConfAccessGraphAWSSyncArguments.SQSQueueURL)
integrationConfAccessGraphAWSSyncCmd.Flag("cloud-trail-bucket", "ARN of the S3 bucket where CloudTrail writes events to.").StringVar(&ccf.IntegrationConfAccessGraphAWSSyncArguments.CloudTrailBucketARN)
integrationConfAccessGraphAWSSyncCmd.Flag("kms-key", "List of KMS Keys used to decrypt SQS and S3 bucket data.").StringsVar(&ccf.IntegrationConfAccessGraphAWSSyncArguments.KMSKeyARNs)
integrationConfAccessGraphAWSSyncCmd.Flag("eks-audit-logs", "Enable collection of EKS audit logs").BoolVar(&ccf.IntegrationConfAccessGraphAWSSyncArguments.EnableEKSAuditLogs)

integrationConfAccessGraphAzureSyncCmd := integrationConfAccessGraphCmd.Command("azure", "Adds required Azure permissions for syncing Azure resources into Access Graph service.")
integrationConfAccessGraphAzureSyncCmd.Flag("managed-identity", "The ID of the managed identity to run the Discovery service.").Required().StringVar(&ccf.IntegrationConfAccessGraphAzureSyncArguments.ManagedIdentity)
Expand Down
Loading