From 6f6e079569fcdb7e0631717fbe269e94f8f7b127 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Fri, 26 Mar 2021 11:26:11 -0700 Subject: [PATCH] fix(codebuild): allow passing the ARN of the Secret in environment variables (#13706) In the SecretsManager-typed environment variables in CodeBuild, the code in the Project class assumed those would be passed as names. As it turns out, CodeBuild also allows passing there entire ARNs of secrets (both partial, and full), and also optional qualifiers, separated by colons, that specify SecretsManager attributes like the JSON key, or the secret version. Add handling of all of these cases. Fixes #12703 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-codebuild/lib/project.ts | 72 +- .../aws-codebuild/test/test.project.ts | 690 ++++++++++++++---- 2 files changed, 587 insertions(+), 175 deletions(-) diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 718f7263ada13..4b03ed8756f9f 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -7,7 +7,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; -import { Aws, Duration, IResource, Lazy, Names, PhysicalName, Resource, SecretValue, Stack, Tokenization } from '@aws-cdk/core'; +import { ArnComponents, Aws, Duration, IResource, Lazy, Names, PhysicalName, Resource, SecretValue, Stack, Token, Tokenization } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IArtifacts } from './artifacts'; import { BuildSpec } from './build-spec'; @@ -707,14 +707,15 @@ export class Project extends ProjectBase { validateNoPlainTextSecrets: boolean = false, principal?: iam.IGrantable): CfnProject.EnvironmentVariableProperty[] { const ret = new Array(); - const ssmVariables = new Array(); - const secretsManagerSecrets = new Array(); + const ssmIamResources = new Array(); + const secretsManagerIamResources = new Array(); for (const [name, envVariable] of Object.entries(environmentVariables)) { + const envVariableValue = envVariable.value?.toString(); const cfnEnvVariable: CfnProject.EnvironmentVariableProperty = { name, type: envVariable.type || BuildEnvironmentVariableType.PLAINTEXT, - value: envVariable.value?.toString(), + value: envVariableValue, }; ret.push(cfnEnvVariable); @@ -733,10 +734,11 @@ export class Project extends ProjectBase { } if (principal) { + const stack = Stack.of(principal); + // save the SSM env variables if (envVariable.type === BuildEnvironmentVariableType.PARAMETER_STORE) { - const envVariableValue = envVariable.value.toString(); - ssmVariables.push(Stack.of(principal).formatArn({ + ssmIamResources.push(stack.formatArn({ service: 'ssm', resource: 'parameter', // If the parameter name starts with / the resource name is not separated with a double '/' @@ -749,27 +751,58 @@ export class Project extends ProjectBase { // save SecretsManager env variables if (envVariable.type === BuildEnvironmentVariableType.SECRETS_MANAGER) { - secretsManagerSecrets.push(Stack.of(principal).formatArn({ - service: 'secretsmanager', - resource: 'secret', - // we don't know the exact ARN of the Secret just from its name, but we can get close - resourceName: `${envVariable.value}-??????`, - sep: ':', - })); + if (Token.isUnresolved(envVariableValue)) { + // the value of the property can be a complex string, separated by ':'; + // see https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html#build-spec.env.secrets-manager + const secretArn = envVariableValue.split(':')[0]; + + // if we are passed a Token, we should assume it's the ARN of the Secret + // (as the name would not work anyway, because it would be the full name, which CodeBuild does not support) + secretsManagerIamResources.push(secretArn); + } else { + // check if the provided value is a full ARN of the Secret + let parsedArn: ArnComponents | undefined; + try { + parsedArn = stack.parseArn(envVariableValue, ':'); + } catch (e) {} + const secretSpecifier: string = parsedArn ? parsedArn.resourceName : envVariableValue; + + // the value of the property can be a complex string, separated by ':'; + // see https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html#build-spec.env.secrets-manager + const secretName = secretSpecifier.split(':')[0]; + const secretIamResourceName = parsedArn + // If we were given an ARN, we don't' know whether the name is full, or partial, + // as CodeBuild supports both ARN forms. + // Because of that, follow the name with a '*', which works for both + ? `${secretName}*` + // 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({ + service: 'secretsmanager', + resource: 'secret', + resourceName: secretIamResourceName, + 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, + })); + } } } } - if (ssmVariables.length !== 0) { + if (ssmIamResources.length !== 0) { principal?.grantPrincipal.addToPrincipalPolicy(new iam.PolicyStatement({ actions: ['ssm:GetParameters'], - resources: ssmVariables, + resources: ssmIamResources, })); } - if (secretsManagerSecrets.length !== 0) { + if (secretsManagerIamResources.length !== 0) { principal?.grantPrincipal.addToPrincipalPolicy(new iam.PolicyStatement({ actions: ['secretsmanager:GetSecretValue'], - resources: secretsManagerSecrets, + resources: secretsManagerIamResources, })); } @@ -1831,7 +1864,10 @@ export interface BuildEnvironmentVariable { * The value of the environment variable. * For plain-text variables (the default), this is the literal value of variable. * For SSM parameter variables, pass the name of the parameter here (`parameterName` property of `IParameter`). - * For SecretsManager variables secrets, pass the secret name here (`secretName` property of `ISecret`). + * For SecretsManager variables secrets, pass either the secret name (`secretName` property of `ISecret`) + * or the secret ARN (`secretArn` property of `ISecret`) here, + * along with optional SecretsManager qualifiers separated by ':', like the JSON key, or the version or stage + * (see https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html#build-spec.env.secrets-manager for details). */ readonly value: any; } diff --git a/packages/@aws-cdk/aws-codebuild/test/test.project.ts b/packages/@aws-cdk/aws-codebuild/test/test.project.ts index 3d7f8e726fd69..80c6940bd79bd 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.project.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.project.ts @@ -765,192 +765,568 @@ export = { }, 'EnvironmentVariables': { - 'can use environment variables from parameter store'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); + 'from SSM': { + 'can use environment variables'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.Project(stack, 'Project', { + source: codebuild.Source.s3({ + bucket: new s3.Bucket(stack, 'Bucket'), + path: 'path', + }), + environment: { + buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('myimage'), + }, + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.PARAMETER_STORE, + value: '/params/param1', + }, + }, + }); - // WHEN - new codebuild.Project(stack, 'Project', { - source: codebuild.Source.s3({ - bucket: new s3.Bucket(stack, 'Bucket'), - path: 'path', - }), - environment: { - buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('myimage'), - }, - environmentVariables: { - 'ENV_VAR1': { - type: codebuild.BuildEnvironmentVariableType.PARAMETER_STORE, - value: '/params/param1', + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + Environment: objectLike({ + EnvironmentVariables: [{ + Name: 'ENV_VAR1', + Type: 'PARAMETER_STORE', + Value: '/params/param1', + }], + }), + })); + + test.done(); + }, + + 'grants the correct read permissions'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.Project(stack, 'Project', { + source: codebuild.Source.s3({ + bucket: new s3.Bucket(stack, 'Bucket'), + path: 'path', + }), + environment: { + buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('myimage'), }, - }, - }); + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.PARAMETER_STORE, + value: '/params/param1', + }, + 'ENV_VAR2': { + type: codebuild.BuildEnvironmentVariableType.PARAMETER_STORE, + value: 'params/param2', + }, + }, + }); - // THEN - expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { - Environment: objectLike({ - EnvironmentVariables: [{ - Name: 'ENV_VAR1', - Type: 'PARAMETER_STORE', - Value: '/params/param1', - }], - }), - })); + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith(objectLike({ + 'Action': 'ssm:GetParameters', + 'Effect': 'Allow', + 'Resource': [{ + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ssm:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':parameter/params/param1', + ], + ], + }, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':ssm:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':parameter/params/param2', + ], + ], + }], + })), + }, + })); - test.done(); - }, + test.done(); + }, - 'grant read permission for parameter store variables'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); + 'does not grant read permissions when variables are not from parameter store'(test: Test) { - // WHEN - new codebuild.Project(stack, 'Project', { - source: codebuild.Source.s3({ - bucket: new s3.Bucket(stack, 'Bucket'), - path: 'path', - }), - environment: { - buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('myimage'), - }, - environmentVariables: { - 'ENV_VAR1': { - type: codebuild.BuildEnvironmentVariableType.PARAMETER_STORE, - value: '/params/param1', + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.Project(stack, 'Project', { + source: codebuild.Source.s3({ + bucket: new s3.Bucket(stack, 'Bucket'), + path: 'path', + }), + environment: { + buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('myimage'), }, - 'ENV_VAR2': { - type: codebuild.BuildEnvironmentVariableType.PARAMETER_STORE, - value: 'params/param2', + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, + value: 'var1-value', + }, }, - }, - }); + }); - // THEN - expect(stack).to(haveResourceLike('AWS::IAM::Policy', { - 'PolicyDocument': { - 'Statement': arrayWith(objectLike({ - 'Action': 'ssm:GetParameters', - 'Effect': 'Allow', - 'Resource': [{ - 'Fn::Join': [ - '', - [ + // THEN + expect(stack).notTo(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith(objectLike({ + 'Action': 'ssm:GetParameters', + 'Effect': 'Allow', + })), + }, + })); + + test.done(); + }, + }, + + 'from SecretsManager': { + 'can be provided as a verbatim secret name'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: 'my-secret', + }, + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': 'my-secret', + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': { + 'Fn::Join': ['', [ 'arn:', - { - Ref: 'AWS::Partition', - }, - ':ssm:', - { - Ref: 'AWS::Region', - }, + { Ref: 'AWS::Partition' }, + ':secretsmanager:', + { Ref: 'AWS::Region' }, ':', - { - Ref: 'AWS::AccountId', - }, - ':parameter/params/param1', - ], - ], + { Ref: 'AWS::AccountId' }, + ':secret:my-secret-??????', + ]], + }, + }), + }, + })); + + test.done(); + }, + + 'can be provided as a verbatim secret name followed by a JSON key'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: 'my-secret:json-key', }, - { - 'Fn::Join': [ - '', - [ + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': 'my-secret:json-key', + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': { + 'Fn::Join': ['', [ 'arn:', - { - Ref: 'AWS::Partition', - }, - ':ssm:', - { - Ref: 'AWS::Region', - }, + { Ref: 'AWS::Partition' }, + ':secretsmanager:', + { Ref: 'AWS::Region' }, ':', - { - Ref: 'AWS::AccountId', - }, - ':parameter/params/param2', - ], - ], - }], - })), - }, - })); + { Ref: 'AWS::AccountId' }, + ':secret:my-secret-??????', + ]], + }, + }), + }, + })); + test.done(); + }, - test.done(); - }, + 'can be provided as a verbatim full secret ARN followed by a JSON key'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); - 'should not grant read permission when variables are not from parameter store'(test: Test) { + // WHEN + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: 'arn:aws:secretsmanager:us-west-2:123456789012:secret:my-secret-123456:json-key', + }, + }, + }); - // GIVEN - const stack = new cdk.Stack(); + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:my-secret-123456:json-key', + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:my-secret-123456*', + }), + }, + })); - // WHEN - new codebuild.Project(stack, 'Project', { - source: codebuild.Source.s3({ - bucket: new s3.Bucket(stack, 'Bucket'), - path: 'path', - }), - environment: { - buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('myimage'), - }, - environmentVariables: { - 'ENV_VAR1': { - type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, - value: 'var1-value', + test.done(); + }, + + 'can be provided as a verbatim partial secret ARN'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret', + }, }, - }, - }); + }); - // THEN - expect(stack).notTo(haveResourceLike('AWS::IAM::Policy', { - 'PolicyDocument': { - 'Statement': arrayWith(objectLike({ - 'Action': 'ssm:GetParameters', - 'Effect': 'Allow', - })), - }, - })); + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': 'arn:aws:secretsmanager:us-west-2:123456789012: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:123456789012:secret:mysecret*', + }), + }, + })); - test.done(); - }, + test.done(); + }, - "grants the Project's Role read permissions to the SecretsManager environment variables"(test: Test) { - // GIVEN - const stack = new cdk.Stack(); + 'can be provided as the ARN attribute of a new Secret'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); - // WHEN - new codebuild.PipelineProject(stack, 'Project', { - environmentVariables: { - 'ENV_VAR1': { - type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, - value: 'my-secret', + // WHEN + const secret = new secretsmanager.Secret(stack, 'Secret'); + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: secret.secretArn, + }, }, - }, - }); + }); - // THEN - expect(stack).to(haveResourceLike('AWS::IAM::Policy', { - 'PolicyDocument': { - 'Statement': arrayWith({ - 'Action': 'secretsmanager:GetSecretValue', - 'Effect': 'Allow', - 'Resource': { - 'Fn::Join': ['', [ - 'arn:', - { Ref: 'AWS::Partition' }, - ':secretsmanager:', - { Ref: 'AWS::Region' }, - ':', - { Ref: 'AWS::AccountId' }, - ':secret:my-secret-??????', - ]], + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': { 'Ref': 'SecretA720EF05' }, + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': { 'Ref': 'SecretA720EF05' }, + }), + }, + })); + + test.done(); + }, + + 'can be provided as the ARN attribute of a new Secret, followed by a JSON key'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const secret = new secretsmanager.Secret(stack, 'Secret'); + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: `${secret.secretArn}:json-key:version-stage`, }, - }), - }, - })); + }, + }); - test.done(); + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': { + 'Fn::Join': ['', [ + { 'Ref': 'SecretA720EF05' }, + ':json-key:version-stage', + ]], + }, + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': { 'Ref': 'SecretA720EF05' }, + }), + }, + })); + + test.done(); + }, + + 'can be provided as the name attribute of a Secret imported by name'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const secret = secretsmanager.Secret.fromSecretNameV2(stack, 'Secret', 'mysecret'); + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: secret.secretName, + }, + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': 'mysecret', + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': { + 'Fn::Join': ['', [ + 'arn:', + { 'Ref': 'AWS::Partition' }, + ':secretsmanager:', + { 'Ref': 'AWS::Region' }, + ':', + { 'Ref': 'AWS::AccountId' }, + ':secret:mysecret-??????', + ]], + }, + }), + }, + })); + + test.done(); + }, + + 'can be provided as the ARN attribute of a Secret imported by partial ARN, followed by a JSON key'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const secret = secretsmanager.Secret.fromSecretPartialArn(stack, 'Secret', + 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret'); + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: `${secret.secretArn}:json-key`, + }, + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret:json-key', + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret*', + }), + }, + })); + + test.done(); + }, + + 'can be provided as the ARN attribute of a Secret imported by complete ARN, followed by a JSON key'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const secret = secretsmanager.Secret.fromSecretCompleteArn(stack, 'Secret', + 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret-123456'); + new codebuild.PipelineProject(stack, 'Project', { + environmentVariables: { + 'ENV_VAR1': { + type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER, + value: `${secret.secretArn}:json-key`, + }, + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'EnvironmentVariables': [ + { + 'Name': 'ENV_VAR1', + 'Type': 'SECRETS_MANAGER', + 'Value': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret-123456:json-key', + }, + ], + }, + })); + + // THEN + expect(stack).to(haveResourceLike('AWS::IAM::Policy', { + 'PolicyDocument': { + 'Statement': arrayWith({ + 'Action': 'secretsmanager:GetSecretValue', + 'Effect': 'Allow', + 'Resource': 'arn:aws:secretsmanager:us-west-2:123456789012:secret:mysecret-123456*', + }), + }, + })); + + test.done(); + }, }, 'should fail creating when using a secret value in a plaintext variable'(test: Test) {