From 30d0b5cfe91756897999e52692b38f0af0ba5e48 Mon Sep 17 00:00:00 2001 From: ialford Date: Wed, 17 Jun 2020 10:50:14 -0400 Subject: [PATCH 1/6] feat(secretsmanager): add grantUpdate for updating secrets closes #8491 --- .../@aws-cdk/aws-secretsmanager/lib/secret.ts | 25 ++++++ .../aws-secretsmanager/test/test.secret.ts | 90 +++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index 91cf18a7a8229..cc31a6f0a1697 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -48,6 +48,12 @@ export interface ISecret extends IResource { */ grantWrite(grantee: iam.IGrantable): iam.Grant; + /** + * Grants updating the secret value to some role. + * + * @param grantee the prinicpal being granted permission. + */ + grantUpdate(grantee: iam.IGrantable): iam.Grant; /** * Adds a rotation schedule to the secret. */ @@ -181,6 +187,25 @@ abstract class SecretBase extends Resource implements ISecret { return result; } + public grantUpdate(grantee: iam.IGrantable): iam.Grant { + // See https://docs.aws.amazon.com/secretsmanager/latest/userguide/auth-and-access_identity-based-policies.html + const result = iam.Grant.addToPrincipal({ + grantee, + actions: ['secretsmanager:UpdateSecret'], + resourceArns: [this.secretArn], + scope: this, + }); + + if (this.encryptionKey) { + // See https://docs.aws.amazon.com/kms/latest/developerguide/services-secrets-manager.html + this.encryptionKey.grantEncrypt( + new kms.ViaServicePrincipal(`secretsmanager.${Stack.of(this).region}.amazonaws.com`, grantee.grantPrincipal), + ); + } + + return result; + } + public get secretValue() { return this.secretValueFromJson(''); } diff --git a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts b/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts index 1b10443ff69e2..9ff15fc3c7022 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts @@ -420,6 +420,96 @@ export = { test.done(); }, + 'grantUpdate'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const secret = new secretsmanager.Secret(stack, 'Secret', {}); + const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); + + // WHEN + secret.grantUpdate(role); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: 'secretsmanager:UpdateSecret', + Effect: 'Allow', + Resource: { Ref: 'SecretA720EF05' }, + }], + }, + })); + test.done(); + }, + + 'grantUpdate with kms'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const key = new kms.Key(stack, 'KMS'); + const secret = new secretsmanager.Secret(stack, 'Secret', { encryptionKey: key }); + const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); + + // WHEN + secret.grantUpdate(role); + + // THEN + const expectStack = expect(stack); + expectStack.to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: 'secretsmanager:UpdateSecret', + Effect: 'Allow', + Resource: { Ref: 'SecretA720EF05' }, + }], + }, + })); + expectStack.to(haveResourceLike('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + {}, + {}, + {}, + { + Action: [ + 'kms:Encrypt', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*', + ], + Condition: { + StringEquals: { + 'kms:ViaService': { + 'Fn::Join': [ + '', + [ + 'secretsmanager.', + { + Ref: 'AWS::Region', + }, + '.amazonaws.com', + ], + ], + }, + }, + }, + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::GetAtt': [ + 'Role1ABCC5F0', + 'Arn', + ], + }, + }, + Resource: '*', + }, + ], + }, + })); + test.done(); + }, + 'secretValue'(test: Test) { // GIVEN const stack = new cdk.Stack(); From 3384f716dda3bd32bec675a91b755ee418343806 Mon Sep 17 00:00:00 2001 From: ialford Date: Wed, 17 Jun 2020 10:55:53 -0400 Subject: [PATCH 2/6] update module README --- packages/@aws-cdk/aws-secretsmanager/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-secretsmanager/README.md b/packages/@aws-cdk/aws-secretsmanager/README.md index fb3a61e920725..9cf7860c8969c 100644 --- a/packages/@aws-cdk/aws-secretsmanager/README.md +++ b/packages/@aws-cdk/aws-secretsmanager/README.md @@ -44,14 +44,15 @@ A secret can set `RemovalPolicy`. If it set to `RETAIN`, that removing a secret ### Grant permission to use the secret to a role You must grant permission to a resource for that resource to be allowed to -use a secret. This can be achieved with the `Secret.grantRead` and/or -`Secret.grantWrite` method, depending on your need: +use a secret. This can be achieved with the `Secret.grantRead`, `Secret.grantWrite`, and/or `Secret.grantUpdate` + method, depending on your need: ```ts const role = new iam.Role(stack, 'SomeRole', { assumedBy: new iam.AccountRootPrincipal() }); const secret = new secretsmanager.Secret(stack, 'Secret'); secret.grantRead(role); secret.grantWrite(role); +secret.grantUpdate(role); ``` If, as in the following example, your secret was created with a KMS key: @@ -60,8 +61,9 @@ const key = new kms.Key(stack, 'KMS'); const secret = new secretsmanager.Secret(stack, 'Secret', { encryptionKey: key }); secret.grantRead(role); secret.grantWrite(role); +secret.grantUpdate(role); ``` -then `Secret.grantRead` and `Secret.grantWrite` will also grant the role the +then `Secret.grantRead`, `Secret.grantWrite`, and `Secret.grantUpdate` will also grant the role the relevant encrypt and decrypt permissions to the KMS key through the SecretsManager service principal. From f49cd9b999ed034642535774b73535fd9195a2e8 Mon Sep 17 00:00:00 2001 From: ialford Date: Wed, 17 Jun 2020 11:34:01 -0400 Subject: [PATCH 3/6] remove trailing whitespace --- packages/@aws-cdk/aws-secretsmanager/lib/secret.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index cc31a6f0a1697..0bef56401fa97 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -50,7 +50,7 @@ export interface ISecret extends IResource { /** * Grants updating the secret value to some role. - * + * * @param grantee the prinicpal being granted permission. */ grantUpdate(grantee: iam.IGrantable): iam.Grant; From 81d1d1c273b321023ff163f2fc91c459b3c50a11 Mon Sep 17 00:00:00 2001 From: ialford Date: Tue, 30 Jun 2020 09:28:53 -0400 Subject: [PATCH 4/6] update README --- packages/@aws-cdk/aws-secretsmanager/README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-secretsmanager/README.md b/packages/@aws-cdk/aws-secretsmanager/README.md index 9cf7860c8969c..540cc9a7fa0be 100644 --- a/packages/@aws-cdk/aws-secretsmanager/README.md +++ b/packages/@aws-cdk/aws-secretsmanager/README.md @@ -44,7 +44,7 @@ A secret can set `RemovalPolicy`. If it set to `RETAIN`, that removing a secret ### Grant permission to use the secret to a role You must grant permission to a resource for that resource to be allowed to -use a secret. This can be achieved with the `Secret.grantRead`, `Secret.grantWrite`, and/or `Secret.grantUpdate` +use a secret. This can be achieved with the `Secret.grantRead` and/or `Secret.grantUpdate` method, depending on your need: ```ts @@ -52,7 +52,6 @@ const role = new iam.Role(stack, 'SomeRole', { assumedBy: new iam.AccountRootPri const secret = new secretsmanager.Secret(stack, 'Secret'); secret.grantRead(role); secret.grantWrite(role); -secret.grantUpdate(role); ``` If, as in the following example, your secret was created with a KMS key: @@ -61,9 +60,8 @@ const key = new kms.Key(stack, 'KMS'); const secret = new secretsmanager.Secret(stack, 'Secret', { encryptionKey: key }); secret.grantRead(role); secret.grantWrite(role); -secret.grantUpdate(role); ``` -then `Secret.grantRead`, `Secret.grantWrite`, and `Secret.grantUpdate` will also grant the role the +then `Secret.grantRead` and `Secret.grantWrite` will also grant the role the relevant encrypt and decrypt permissions to the KMS key through the SecretsManager service principal. From 716a2bd76fc6c6d9814e482e73f96a1dbc65f107 Mon Sep 17 00:00:00 2001 From: ialford Date: Tue, 30 Jun 2020 09:29:20 -0400 Subject: [PATCH 5/6] add updateSecret to grantWrite --- .../@aws-cdk/aws-secretsmanager/lib/secret.ts | 29 ++----------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index 0bef56401fa97..c0abdd48832c8 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -42,18 +42,12 @@ export interface ISecret extends IResource { grantRead(grantee: iam.IGrantable, versionStages?: string[]): iam.Grant; /** - * Grants writing the secret value to some role. + * Grants writing and updating the secret value to some role. * * @param grantee the principal being granted permission. */ grantWrite(grantee: iam.IGrantable): iam.Grant; - /** - * Grants updating the secret value to some role. - * - * @param grantee the prinicpal being granted permission. - */ - grantUpdate(grantee: iam.IGrantable): iam.Grant; /** * Adds a rotation schedule to the secret. */ @@ -172,26 +166,7 @@ abstract class SecretBase extends Resource implements ISecret { // See https://docs.aws.amazon.com/secretsmanager/latest/userguide/auth-and-access_identity-based-policies.html const result = iam.Grant.addToPrincipal({ grantee, - actions: ['secretsmanager:PutSecretValue'], - resourceArns: [this.secretArn], - scope: this, - }); - - if (this.encryptionKey) { - // See https://docs.aws.amazon.com/kms/latest/developerguide/services-secrets-manager.html - this.encryptionKey.grantEncrypt( - new kms.ViaServicePrincipal(`secretsmanager.${Stack.of(this).region}.amazonaws.com`, grantee.grantPrincipal), - ); - } - - return result; - } - - public grantUpdate(grantee: iam.IGrantable): iam.Grant { - // See https://docs.aws.amazon.com/secretsmanager/latest/userguide/auth-and-access_identity-based-policies.html - const result = iam.Grant.addToPrincipal({ - grantee, - actions: ['secretsmanager:UpdateSecret'], + actions: ['secretsmanager:PutSecretValue', 'secretsmanager:UpdateSecret'], resourceArns: [this.secretArn], scope: this, }); From 085d7fa3c67fb42418f51f8dd1ba3c8bda99446e Mon Sep 17 00:00:00 2001 From: ialford Date: Tue, 30 Jun 2020 09:29:47 -0400 Subject: [PATCH 6/6] update unit tests --- .../aws-secretsmanager/test/test.secret.ts | 100 ++---------------- 1 file changed, 8 insertions(+), 92 deletions(-) diff --git a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts b/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts index 9ff15fc3c7022..89767231ee750 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts @@ -344,7 +344,10 @@ export = { PolicyDocument: { Version: '2012-10-17', Statement: [{ - Action: 'secretsmanager:PutSecretValue', + Action: [ + 'secretsmanager:PutSecretValue', + 'secretsmanager:UpdateSecret', + ], Effect: 'Allow', Resource: { Ref: 'SecretA720EF05' }, }], @@ -369,97 +372,10 @@ export = { PolicyDocument: { Version: '2012-10-17', Statement: [{ - Action: 'secretsmanager:PutSecretValue', - Effect: 'Allow', - Resource: { Ref: 'SecretA720EF05' }, - }], - }, - })); - expectStack.to(haveResourceLike('AWS::KMS::Key', { - KeyPolicy: { - Statement: [ - {}, - {}, - {}, - { - Action: [ - 'kms:Encrypt', - 'kms:ReEncrypt*', - 'kms:GenerateDataKey*', - ], - Condition: { - StringEquals: { - 'kms:ViaService': { - 'Fn::Join': [ - '', - [ - 'secretsmanager.', - { - Ref: 'AWS::Region', - }, - '.amazonaws.com', - ], - ], - }, - }, - }, - Effect: 'Allow', - Principal: { - AWS: { - 'Fn::GetAtt': [ - 'Role1ABCC5F0', - 'Arn', - ], - }, - }, - Resource: '*', - }, - ], - }, - })); - test.done(); - }, - - 'grantUpdate'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const secret = new secretsmanager.Secret(stack, 'Secret', {}); - const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); - - // WHEN - secret.grantUpdate(role); - - // THEN - expect(stack).to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Version: '2012-10-17', - Statement: [{ - Action: 'secretsmanager:UpdateSecret', - Effect: 'Allow', - Resource: { Ref: 'SecretA720EF05' }, - }], - }, - })); - test.done(); - }, - - 'grantUpdate with kms'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const key = new kms.Key(stack, 'KMS'); - const secret = new secretsmanager.Secret(stack, 'Secret', { encryptionKey: key }); - const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); - - // WHEN - secret.grantUpdate(role); - - // THEN - const expectStack = expect(stack); - expectStack.to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Version: '2012-10-17', - Statement: [{ - Action: 'secretsmanager:UpdateSecret', + Action: [ + 'secretsmanager:PutSecretValue', + 'secretsmanager:UpdateSecret', + ], Effect: 'Allow', Resource: { Ref: 'SecretA720EF05' }, }],