Skip to content

Commit

Permalink
fix(core): remove cdk.Secret (#2068)
Browse files Browse the repository at this point in the history
`cdk.Secret` was left over from when we thought we were going to do
secrets differently. Today, we model secret values as strings, which
can be retrieved from one of these:

- `ssm.ParameterStoreSecureString.stringValue`
- `secretsmanager.SecretString.stringValue`
- `cdk.CfnParameter.stringValue` (but don't do that, because the secret
  will be readable from CloudFormation logs)

Fixes #2064.

BREAKING CHANGE: Replace use of `cdk.Secret` with
`secretsmanager.SecretString` (preferred) or
`ssm.ParameterStoreSecureString`.
  • Loading branch information
rix0rrr authored Apr 1, 2019
1 parent 4819ff4 commit b53d04d
Show file tree
Hide file tree
Showing 21 changed files with 87 additions and 164 deletions.
2 changes: 1 addition & 1 deletion packages/@aws-cdk/alexa-ask/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

```ts
const alexaAsk = require('@aws-cdk/alexa-ask');
```
```
2 changes: 1 addition & 1 deletion packages/@aws-cdk/app-delivery/test/integ.cicd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const source = new cpactions.GitHubSourceAction({
actionName: 'GitHub',
owner: 'awslabs',
repo: 'aws-cdk',
oauthToken: new cdk.Secret('DummyToken'),
oauthToken: cdk.Secret.plainText('DummyToken'),
pollForSourceChanges: true,
outputArtifactName: 'Artifact_CICDGitHubF8BA7ADD',
});
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions packages/@aws-cdk/aws-codebuild/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ Example:
const gitHubSource = new codebuild.GitHubSource({
owner: 'awslabs',
repo: 'aws-cdk',
oauthToken: new cdk.SecretParameter(this, 'GitHubOAuthToken', {
ssmParameter: 'my-github-token',
}).value,
oauthToken: new secretsmanager.SecretString(this, 'GitHubOAuthToken', {
secretId: 'my-github-token',
}).stringValue,
webhook: true, // optional, default: false
});
```
Expand Down
10 changes: 6 additions & 4 deletions packages/@aws-cdk/aws-codebuild/lib/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export interface GitHubSourceProps extends GitBuildSourceProps {
* Note that you need to give CodeBuild permissions to your GitHub account in order for the token to work.
* That is a one-time operation that can be done through the AWS Console for CodeBuild.
*/
readonly oauthToken: cdk.Secret;
readonly oauthToken: string;

/**
* Whether to create a webhook that will trigger a build every time a commit is pushed to the GitHub repository.
Expand All @@ -236,12 +236,13 @@ export interface GitHubSourceProps extends GitBuildSourceProps {
export class GitHubSource extends GitBuildSource {
public readonly type: SourceType = SourceType.GitHub;
private readonly httpsCloneUrl: string;
private readonly oauthToken: cdk.Secret;
private readonly oauthToken: string;
private readonly reportBuildStatus: boolean;
private readonly webhook?: boolean;

constructor(props: GitHubSourceProps) {
super(props);
cdk.Secret.assertSafeSecret(props.oauthToken, 'oauthToken');
this.httpsCloneUrl = `https://github.com/${props.owner}/${props.repo}.git`;
this.oauthToken = props.oauthToken;
this.webhook = props.webhook;
Expand Down Expand Up @@ -277,7 +278,7 @@ export interface GitHubEnterpriseSourceProps extends GitBuildSourceProps {
/**
* The OAuth token used to authenticate when cloning the git repository.
*/
readonly oauthToken: cdk.Secret;
readonly oauthToken: string;

/**
* Whether to ignore SSL errors when connecting to the repository.
Expand All @@ -293,11 +294,12 @@ export interface GitHubEnterpriseSourceProps extends GitBuildSourceProps {
export class GitHubEnterpriseSource extends GitBuildSource {
public readonly type: SourceType = SourceType.GitHubEnterprise;
private readonly httpsCloneUrl: string;
private readonly oauthToken: cdk.Secret;
private readonly oauthToken: string;
private readonly ignoreSslErrors?: boolean;

constructor(props: GitHubEnterpriseSourceProps) {
super(props);
cdk.Secret.assertSafeSecret(props.oauthToken, 'oauthToken');
this.httpsCloneUrl = props.httpsCloneUrl;
this.oauthToken = props.oauthToken;
this.ignoreSslErrors = props.ignoreSslErrors;
Expand Down
8 changes: 4 additions & 4 deletions packages/@aws-cdk/aws-codebuild/test/test.project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export = {
owner: 'testowner',
repo: 'testrepo',
cloneDepth: 3,
oauthToken: new cdk.Secret("test_oauth_token")
oauthToken: cdk.Secret.plainText("test_oauth_token"),
})
});

Expand Down Expand Up @@ -88,7 +88,7 @@ export = {
source: new codebuild.GitHubSource({
owner: 'testowner',
repo: 'testrepo',
oauthToken: new cdk.Secret('test_oauth_token'),
oauthToken: cdk.Secret.plainText("test_oauth_token"),
reportBuildStatus: false,
})
});
Expand All @@ -112,7 +112,7 @@ export = {
source: new codebuild.GitHubSource({
owner: 'testowner',
repo: 'testrepo',
oauthToken: new cdk.Secret('test_oauth_token'),
oauthToken: cdk.Secret.plainText("test_oauth_token"),
webhook: true,
})
});
Expand All @@ -138,7 +138,7 @@ export = {
httpsCloneUrl: 'https://github.testcompany.com/testowner/testrepo',
ignoreSslErrors: true,
cloneDepth: 4,
oauthToken: new cdk.Secret("test_oauth_token")
oauthToken: cdk.Secret.plainText("test_oauth_token"),
})
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ export interface AlexaSkillDeployActionProps extends codepipeline.CommonActionPr
/**
* The client id of the developer console token
*/
readonly clientId: cdk.Secret;
readonly clientId: string;

/**
* The client secret of the developer console token
*/
readonly clientSecret: cdk.Secret;
readonly clientSecret: string;

/**
* The refresh token of the developer console token
*/
readonly refreshToken: cdk.Secret;
readonly refreshToken: string;

/**
* The Alexa skill id
Expand All @@ -41,6 +41,9 @@ export interface AlexaSkillDeployActionProps extends codepipeline.CommonActionPr
*/
export class AlexaSkillDeployAction extends codepipeline.DeployAction {
constructor(props: AlexaSkillDeployActionProps) {
cdk.Secret.assertSafeSecret(props.clientSecret, 'clientSecret');
cdk.Secret.assertSafeSecret(props.refreshToken, 'refreshToken');

super({
...props,
artifactBounds: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,12 @@ export interface GitHubSourceActionProps extends codepipeline.CommonActionProps
/**
* A GitHub OAuth token to use for authentication.
*
* It is recommended to use a `SecretParameter` to obtain the token from the SSM
* Parameter Store:
* It is recommended to use a Secrets Manager `SecretString` to obtain the token:
*
* const oauth = new cdk.SecretParameter(this, 'GitHubOAuthToken', { ssmParameter: 'my-github-token' });
* const oauth = new secretsmanager.SecretString(this, 'GitHubOAuthToken', { secretId: 'my-github-token' });
* new GitHubSource(this, 'GitHubAction', { oauthToken: oauth.value, ... });
*/
readonly oauthToken: cdk.Secret;
readonly oauthToken: string;

/**
* Whether AWS CodePipeline should poll for source changes.
Expand All @@ -55,6 +54,8 @@ export class GitHubSourceAction extends codepipeline.SourceAction {
private readonly props: GitHubSourceActionProps;

constructor(props: GitHubSourceActionProps) {
cdk.Secret.assertSafeSecret(props.oauthToken, 'oauthToken');

super({
...props,
owner: 'ThirdParty',
Expand All @@ -77,7 +78,7 @@ export class GitHubSourceAction extends codepipeline.SourceAction {
new codepipeline.CfnWebhook(info.scope, 'WebhookResource', {
authentication: 'GITHUB_HMAC',
authenticationConfiguration: {
secretToken: this.props.oauthToken.toString(),
secretToken: this.props.oauthToken,
},
filters: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ const deployStage = {
actionName: 'DeploySkill',
runOrder: 1,
inputArtifact: sourceAction.outputArtifact,
clientId: new cdk.Secret('clientId'),
clientSecret: new cdk.Secret('clientSecret'),
refreshToken: new cdk.Secret('refreshToken'),
clientId: 'clientId',
clientSecret: cdk.Secret.plainText('clientSecret'),
refreshToken: cdk.Secret.plainText('refreshToken'),
skillId: 'amzn1.ask.skill.12345678-1234-1234-1234-123456789012',
}),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export = {
'github action uses ThirdParty owner'(test: Test) {
const stack = new cdk.Stack();

const secret = new cdk.SecretParameter(stack, 'GitHubToken', { ssmParameter: 'my-token' });
const secret = new cdk.CfnParameter(stack, 'GitHubToken', { type: 'String', default: 'my-token' });

const p = new codepipeline.Pipeline(stack, 'P');

Expand All @@ -80,7 +80,7 @@ export = {
runOrder: 8,
outputArtifactName: 'A',
branch: 'branch',
oauthToken: secret.value,
oauthToken: secret.stringValue,
owner: 'foo',
repo: 'bar'
}),
Expand Down Expand Up @@ -122,7 +122,7 @@ export = {
"Repo": "bar",
"Branch": "branch",
"OAuthToken": {
"Ref": "GitHubTokenParameterBB166B9D"
"Ref": "GitHubToken"
},
"PollForSourceChanges": false
},
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-lambda/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-serverless/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions packages/@aws-cdk/cdk/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,11 @@ export * from './dynamic-reference';
export * from './tag';
export * from './removal-policy';
export * from './arn';
export * from './secret';

export * from './app';
export * from './context';
export * from './environment';

export * from './runtime';

export * from './synthesis';
export * from './secret';
export * from './synthesis';
115 changes: 34 additions & 81 deletions packages/@aws-cdk/cdk/lib/secret.ts
Original file line number Diff line number Diff line change
@@ -1,94 +1,47 @@
import { CfnParameter } from './cfn-parameter';
import { Construct } from './construct';
import { Token } from './token';
import { Token } from "./token";
import { unresolved } from "./unresolved";

/**
* A token that represents a value that's expected to be a secret, like
* passwords and keys.
* Work with secret values in the CDK
*
* It is recommended to use the `SecretParameter` construct in order to import
* secret values from the SSM Parameter Store instead of storing them in your
* code.
* Secret values in the CDK (such as those retrieved from SecretsManager) are
* represented as regular strings, just like other values that are only
* available at deployment time.
*
* However, you can also just pass in values, like any other token: `new Secret('bla')`
* To help you avoid accidental mistakes which would lead to you putting your
* secret values directly into a CloudFormation template, constructs that take
* secret values will not allow you to pass in a literal secret value. They do
* so by calling `Secret.assertSafeSecret()`.
*
* You can escape the check by calling `Secret.plainTex()`, but doing
* so is highly discouraged.
*/
export class Secret extends Token { }

export interface SecretParameterProps {
/**
* The name of the SSM parameter where the secret value is stored.
*/
readonly ssmParameter: string;

/**
* A string of up to 4000 characters that describes the parameter.
* @default No description
*/
readonly description?: string;

/**
* A regular expression that represents the patterns to allow for String types.
*/
readonly allowedPattern?: string;

/**
* An array containing the list of values allowed for the parameter.
*/
readonly allowedValues?: string[];

export class Secret {
/**
* A string that explains a constraint when the constraint is violated.
* For example, without a constraint description, a parameter that has an allowed
* pattern of [A-Za-z0-9]+ displays the following error message when the user specifies
* an invalid value:
* Validate that a given secret value is not a literal
*
* If the value is a literal, throw an error.
*/
readonly constraintDescription?: string;
public static assertSafeSecret(secretValue: string, parameterName?: string) {
if (!unresolved(secretValue)) {
const theParameter = parameterName ? `'${parameterName}'` : 'The value';

/**
* An integer value that determines the largest number of characters you want to allow for String types.
*/
readonly maxLength?: number;
// tslint:disable-next-line:max-line-length
throw new Error(`${theParameter} should be a secret. Store it in SecretsManager or Systems Manager Parameter Store and retrieve it from there. Secret.plainTex() can be used to bypass this check, but do so for testing purposes only.`);
}
}

/**
* An integer value that determines the smallest number of characters you want to allow for String types.
* Construct a literal secret value for use with secret-aware constructs
*
* *Do not use this method for any secrets that you care about.*
*
* The only reasonable use case for using this method is when you are testing.
*/
readonly minLength?: number;
}

/**
* Defines a secret value resolved from the Systems Manager (SSM) Parameter
* Store during deployment. This is useful for referencing values that you do
* not wish to include in your code base, such as secrets, passwords and keys.
*
* This construct will add a CloudFormation parameter to your template bound to
* an SSM parameter (of type "AWS::SSM::Parameter::Value<String>"). Deployment
* will fail if the value doesn't exist in the target environment.
*
* Important: For values other than secrets, prefer to use the
* `SSMParameterProvider` which resolves SSM parameter in design-time, and
* ensures that stack deployments are deterministic.
*/
export class SecretParameter extends Construct {
/**
* The value of the secret parameter.
*/
public value: Secret;

constructor(scope: Construct, id: string, props: SecretParameterProps) {
super(scope, id);

const param = new CfnParameter(this, 'Parameter', {
type: 'AWS::SSM::Parameter::Value<String>',
default: props.ssmParameter,
description: props.description,
allowedPattern: props.allowedPattern,
allowedValues: props.allowedValues,
constraintDescription: props.constraintDescription,
maxLength: props.maxLength,
minLength: props.minLength,
noEcho: true,
});
public static plainText(secret: string): string {
return new Token(() => secret).toString();
}

this.value = new Secret(param.ref);
private constructor() {
}
}
}
2 changes: 1 addition & 1 deletion packages/@aws-cdk/cdk/lib/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,4 @@ export interface IResolvedValuePostProcessor {
*/
export function isResolvedValuePostProcessor(x: any): x is IResolvedValuePostProcessor {
return x.postProcess !== undefined;
}
}
Loading

0 comments on commit b53d04d

Please sign in to comment.