From 38544656e4c9b196bd147aee4c85de4c02e3e47d Mon Sep 17 00:00:00 2001 From: Maximilian Prusch Date: Sat, 17 Apr 2021 01:10:07 +0200 Subject: [PATCH] feat(CodeBuild): add kms:Decrypt action for secrets from other accounts When providing a secretArn from a another account for the EnvironmentVariables allow kms:Decrypt action for any key. This enables CodeBuild to get the secret without any further policy changes by the user. fixes #14043 --- .../@aws-cdk/aws-codebuild/lib/project.ts | 23 ++++- .../aws-codebuild/test/test.project.ts | 87 ++++++++++++++++++- 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 60212c1bff6ab..25acddcbb7ca4 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -721,6 +721,7 @@ export class Project extends ProjectBase { const ret = new Array(); const ssmIamResources = new Array(); const secretsManagerIamResources = new Array(); + const kmsIamResources = new Array(); for (const [name, envVariable] of Object.entries(environmentVariables)) { const envVariableValue = envVariable.value?.toString(); @@ -790,7 +791,7 @@ export class Project extends ProjectBase { // If we were given just a name, it must be partial, as CodeBuild doesn't support providing full names. // In this case, we need to accommodate for the generated suffix in the IAM resource name : `${secretName}-??????`; - secretsManagerIamResources.push(Stack.of(principal).formatArn({ + secretsManagerIamResources.push(stack.formatArn({ service: 'secretsmanager', resource: 'secret', resourceName: secretIamResourceName, @@ -800,6 +801,20 @@ export class Project extends ProjectBase { account: parsedArn?.account, region: parsedArn?.region, })); + // if secret comes from another account, SecretsManager will need to access + // KMS on the other account as well to be able to get the secret + if (parsedArn?.account !== stack.account) { + kmsIamResources.push(stack.formatArn({ + service: 'kms', + resource: 'key', + resourceName: '*', + sep: '/', + // if we were given an ARN, we need to use the provided partition/account/region + partition: parsedArn?.partition, + account: parsedArn?.account, + region: parsedArn?.region, + })); + } } } } @@ -817,6 +832,12 @@ export class Project extends ProjectBase { resources: secretsManagerIamResources, })); } + if (kmsIamResources.length !== 0) { + principal?.grantPrincipal.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: ['kms:Decrypt'], + resources: kmsIamResources, + })); + } return ret; } diff --git a/packages/@aws-cdk/aws-codebuild/test/test.project.ts b/packages/@aws-cdk/aws-codebuild/test/test.project.ts index a8c6ea0abd17a..4b9bf3beb7bba 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.project.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.project.ts @@ -1021,7 +1021,10 @@ export = { 'can be provided as a verbatim full secret ARN followed by a JSON key'(test: Test) { // GIVEN - const stack = new cdk.Stack(); + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'ProjectStack', { + env: { account: '123456789012' }, + }); // WHEN new codebuild.PipelineProject(stack, 'Project', { @@ -1057,12 +1060,26 @@ export = { }, })); + // THEN + expect(stack).to(not(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'kms:Decrypt', + 'Effect': 'Allow', + 'Resource': 'arn:aws:kms:us-west-2:123456789012:key/*', + }), + }, + }))); + test.done(); }, 'can be provided as a verbatim partial secret ARN'(test: Test) { // GIVEN - const stack = new cdk.Stack(); + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'ProjectStack', { + env: { account: '123456789012' }, + }); // WHEN new codebuild.PipelineProject(stack, 'Project', { @@ -1098,6 +1115,72 @@ export = { }, })); + // THEN + expect(stack).to(not(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'kms:Decrypt', + 'Effect': 'Allow', + 'Resource': 'arn:aws:kms:us-west-2:123456789012:key/*', + }), + }, + }))); + + test.done(); + }, + + 'can be provided as a verbatim partial secret ARN from another account'(test: Test) { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'ProjectStack', { + env: { account: '123456789012' }, + }); + + // WHEN + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: 'arn:aws:secretsmanager:us-west-2:901234567890:secret:mysecret', + }, + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': 'arn:aws:secretsmanager:us-west-2:901234567890:secret:mysecret', + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': 'arn:aws:secretsmanager:us-west-2:901234567890:secret:mysecret*', + }), + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'kms:Decrypt', + 'Effect': 'Allow', + 'Resource': 'arn:aws:kms:us-west-2:901234567890:key/*', + }), + }, + })); + test.done(); },