Skip to content

Commit

Permalink
Remove PIP keys from the cache key and hash function
Browse files Browse the repository at this point in the history
  • Loading branch information
Kevin Peterson committed Nov 9, 2023
1 parent 5256de7 commit 54e89c9
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 2 deletions.
18 changes: 16 additions & 2 deletions packages/aws-cdk-lib/core/lib/asset-staging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ export class AssetStaging extends Construct {

// If we're bundling an asset, include the bundling configuration in the hash
if (bundling) {
hash.update(JSON.stringify(bundling));
hash.update(JSON.stringify(bundling, sanitizeHashValue));
}

return hash.digest('hex');
Expand Down Expand Up @@ -538,7 +538,7 @@ function determineHashType(assetHashType?: AssetHashType, customSourceFingerprin
*/
function calculateCacheKey<A extends object>(props: A): string {
return crypto.createHash('sha256')
.update(JSON.stringify(sortObject(props)))
.update(JSON.stringify(sortObject(props), sanitizeHashValue))
.digest('hex');
}

Expand All @@ -556,6 +556,20 @@ function sortObject(object: { [key: string]: any }): { [key: string]: any } {
return ret;
}

/**
* Remove the auth token from the URLs if present.
*/
function sanitizeHashValue(key: string, value: any): any {
if (key === 'PIP_INDEX_URL' || key === 'PIP_EXTRA_INDEX_URL') {
let url = new URL(value);
if (url.password) {
url.password = '';
return url.toString();
}
}
return value;
}

/**
* Returns the single archive file of a directory or undefined
*/
Expand Down
109 changes: 109 additions & 0 deletions packages/aws-cdk-lib/core/test/staging.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,53 @@ describe('staging', () => {
]);
});

test('bundler ignores secret tokens in code artifact URLs', () => {
// GIVEN
const app = new App({ context: { [cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: false } });
const stack = new Stack(app, 'stack');
const directory = path.join(__dirname, 'fs', 'fixtures', 'test1');

// WHEN
new AssetStaging(stack, 'Asset', {
sourcePath: directory,
bundling: {
image: DockerImage.fromRegistry('alpine'),
command: [DockerStubCommand.SUCCESS],
environment: {
PIP_INDEX_URL: 'https://aws:MY_SECRET_TOKEN@your-code-repo.d.codeartifact.us-west-2.amazonaws.com/pypi/python/simple/',
},
},
});

new AssetStaging(stack, 'AssetWithDifferentBundlingOptions', {
sourcePath: directory,
bundling: {
image: DockerImage.fromRegistry('alpine'),
command: [DockerStubCommand.SUCCESS],
environment: {
PIP_INDEX_URL: 'https://aws:MY_OTHER_SECRET_TOKEN@your-code-repo.d.codeartifact.us-west-2.amazonaws.com/pypi/python/simple/',
},
},
});

// THEN
const assembly = app.synth();

// We're testing that docker was run once, only for the first Asset, since the only difference is the token.
expect(
readDockerStubInputConcat()).toEqual(
`run --rm ${USER_ARG} -v /input:/asset-input:delegated -v /output:/asset-output:delegated --env PIP_INDEX_URL=https://aws:MY_SECRET_TOKEN@your-code-repo.d.codeartifact.us-west-2.amazonaws.com/pypi/python/simple/ -w /asset-input alpine DOCKER_STUB_SUCCESS`,
);

expect(fs.readdirSync(assembly.directory)).toEqual([
'asset.2de2347dd01e3f43a463652635acaae09539cdf32769d9a60ac0ad4622b1e943', // 'Asset'
'cdk.out',
'manifest.json',
'stack.template.json',
'tree.json',
]);
});

test('bundler outputs to intermediate dir and renames to asset', () => {
// GIVEN
const app = new App({ context: { [cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: false } });
Expand Down Expand Up @@ -609,6 +656,68 @@ describe('staging', () => {
]);
});

test('bundler re-uses assets from previous synths, ignoring tokens', () => {
// GIVEN
const TEST_OUTDIR = path.join(__dirname, 'cdk.out');
if (fs.existsSync(TEST_OUTDIR)) {
fs.removeSync(TEST_OUTDIR);
}

const app = new App({ outdir: TEST_OUTDIR, context: { [cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: false } });
const stack = new Stack(app, 'stack');
const directory = path.join(__dirname, 'fs', 'fixtures', 'test1');

// WHEN
new AssetStaging(stack, 'Asset', {
sourcePath: directory,
bundling: {
image: DockerImage.fromRegistry('alpine'),
command: [DockerStubCommand.SUCCESS],
environment: {
PIP_EXTRA_INDEX_URL: 'https://aws:MY_SECRET_TOKEN@your-code-repo.d.codeartifact.us-west-2.amazonaws.com/pypi/python/simple/',
},
},
});

// Clear asset hash cache to show that during the second synth bundling
// will consider the existing bundling dir (file system cache).
AssetStaging.clearAssetHashCache();

// GIVEN
const app2 = new App({ outdir: TEST_OUTDIR, context: { [cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: false } });
const stack2 = new Stack(app2, 'stack');

// WHEN
new AssetStaging(stack2, 'Asset', {
sourcePath: directory,
bundling: {
image: DockerImage.fromRegistry('alpine'),
command: [DockerStubCommand.SUCCESS],
environment: {
PIP_EXTRA_INDEX_URL: 'https://aws:MY_OTHER_SECRET_TOKEN@your-code-repo.d.codeartifact.us-west-2.amazonaws.com/pypi/python/simple/',
},
},
});

// THEN
const appAssembly = app.synth();
const app2Assembly = app2.synth();

expect(
readDockerStubInputConcat()).toEqual(
`run --rm ${USER_ARG} -v /input:/asset-input:delegated -v /output:/asset-output:delegated --env PIP_EXTRA_INDEX_URL=https://aws:MY_SECRET_TOKEN@your-code-repo.d.codeartifact.us-west-2.amazonaws.com/pypi/python/simple/ -w /asset-input alpine DOCKER_STUB_SUCCESS`,
);

expect(appAssembly.directory).toEqual(app2Assembly.directory);
expect(fs.readdirSync(appAssembly.directory)).toEqual([
'asset.ec1d4062c578dacd630d64166a7d1efcd472e570e085a63f8857f6c674491bac',
'cdk.out',
'manifest.json',
'stack.template.json',
'tree.json',
]);
});

test('bundling throws when /asset-output is empty', () => {
// GIVEN
const app = new App();
Expand Down

0 comments on commit 54e89c9

Please sign in to comment.