Skip to content

Commit

Permalink
fix(secretsmanager): secretName for owned secrets includes suffix (un…
Browse files Browse the repository at this point in the history
…der feature flag)

Proper (tokenized) secret names for owned secrets was introduced in #11202. This
caused `Secret.secretName` to return the full resource name (secret name + suffix)
for owned secrets. This was done to ensure that the secretName was a Tokenized
value that would create a dependency between the Secret and its consumer.

However, Secrets Manager's DescribeSecret API does not accept the resourceName
as an input; it accepts the full ARN, a partial ARN (without the suffix), or the
secret name (without the suffix). This leads to several integrations with other
services or custom scripts to fail when using the secretName from an owned
Secret.

For owned secrets, we can parse the resourceName to get the secretName based on
either (a) knowing the secret name or (b) knowing the format that CloudFormation
uses when secret names aren't specified. This fix introduces proper parsing of
secret names for owned secrets (behind a feature flag).

BREAKING CHANGE: (feature flag) Secret.secretName for owned secrets will now
only the secret name (without suffix) and not the full resource name. This is
enabled through the `@aws-cdk/secretsmanager:parseOwnedSecretName` flag.

fixes #11727
  • Loading branch information
njlynch committed Nov 26, 2020
1 parent e236778 commit 70dbba1
Show file tree
Hide file tree
Showing 8 changed files with 615 additions and 17 deletions.
41 changes: 39 additions & 2 deletions packages/@aws-cdk/aws-secretsmanager/lib/secret.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as iam from '@aws-cdk/aws-iam';
import * as kms from '@aws-cdk/aws-kms';
import { IResource, RemovalPolicy, Resource, SecretValue, Stack, Token } from '@aws-cdk/core';
import { FeatureFlags, Fn, IResource, RemovalPolicy, Resource, SecretValue, Stack, Token } from '@aws-cdk/core';
import * as cxapi from '@aws-cdk/cx-api';
import { IConstruct, Construct } from 'constructs';
import { ResourcePolicy } from './policy';
import { RotationSchedule, RotationScheduleOptions } from './rotation-schedule';
Expand Down Expand Up @@ -437,7 +438,10 @@ export class Secret extends SecretBase {
});

this.encryptionKey = props.encryptionKey;
this.secretName = parseSecretName(this, this.secretArn);
const parseOwnedSecretName = FeatureFlags.of(this).isEnabled(cxapi.SECRETS_MANAGER_PARSE_OWNED_SECRET_NAME);
this.secretName = parseOwnedSecretName
? parseSecretNameForOwnedSecret(this, this.secretArn, props.secretName)
: parseSecretName(this, this.secretArn);

// @see https://docs.aws.amazon.com/kms/latest/developerguide/services-secrets-manager.html#asm-authz
const principal =
Expand Down Expand Up @@ -707,6 +711,39 @@ function parseSecretName(construct: IConstruct, secretArn: string) {
throw new Error('invalid ARN format; no secret name provided');
}

/**
* Parses the secret name from the ARN of an owned secret. With owned secrets we know that a few things we don't with imported secrets:
* - The ARN is guaranteed to be a full ARN, with suffix.
* - The name -- if provided -- will tell us how many hyphens to expect in the final secret name.
* - If the name is not provided, we know the format used by CloudFormation for auto-generated names.
*
* Note: This is done rather than just returning the secret name passed in by the user to keep the relationship
* explicit between the Secret and wherever the secretName might be used (i.e., using Tokens).
*/
function parseSecretNameForOwnedSecret(construct: IConstruct, secretArn: string, secretName?: string) {
const resourceName = Stack.of(construct).parseArn(secretArn, ':').resourceName;
if (!resourceName) {
throw new Error('invalid ARN format; no secret name provided');
}

// Secret name was explicitly provided, but is unresolved; best option is to use it directly.
// If it came from another Secret, it should (hopefully) already be properly formatted.
if (secretName && Token.isUnresolved(secretName)) {
return secretName;
}

// If no secretName was provided, the name will be automatically generated by CloudFormation.
// The autogenerated names have the form of `${logicalID}-${random}`.
// Otherwise, we can use the existing secretName to determine how to parse the resulting resourceName.
const secretNameHyphenatedSegments = secretName ? secretName.split('-').length : 2;
// 2 => [0, 1]
const segmentIndexes = [...new Array(secretNameHyphenatedSegments)].map((_, i) => i);

// Create the secret name from the resource name by joining all the known segments together.
// This should have the effect of stripping the final hyphen and SecretManager suffix.
return Fn.join('-', segmentIndexes.map(i => Fn.select(i, Fn.split('-', resourceName))));
}

/** Performs a best guess if an ARN is complete, based on if it ends with a 6-character suffix. */
function arnIsComplete(secretArn: string): boolean {
return Token.isUnresolved(secretArn) || /-[a-z0-9]{6}$/i.test(secretArn);
Expand Down
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-secretsmanager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-cdk/aws-sam": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/cx-api": "0.0.0",
"constructs": "^3.2.0"
},
"peerDependencies": {
Expand All @@ -95,6 +96,7 @@
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-cdk/aws-sam": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/cx-api": "0.0.0",
"constructs": "^3.2.0"
},
"engines": {
Expand Down
Loading

0 comments on commit 70dbba1

Please sign in to comment.