Skip to content

Commit 412f626

Browse files
committed
fix(cdk): resolve publickey deletion on stack update
1 parent 67007d0 commit 412f626

3 files changed

Lines changed: 4182 additions & 3266 deletions

File tree

packages/cdk/src/methods/secret.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { SecretsManagerClient, ListSecretsCommand } from '@aws-sdk/client-secrets-manager';
22
import { IGrantable } from 'aws-cdk-lib/aws-iam';
3-
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
3+
import { ISecret, Secret } from 'aws-cdk-lib/aws-secretsmanager';
44
import { SecretValue } from 'aws-cdk-lib';
55
import { nanoid } from 'nanoid';
66

77
import { DotStack } from '../constructs/Stack';
88

9+
export { ISecret, Secret, SecretValue };
10+
911
interface AddSecretOptions {
1012
consumers?: IGrantable[];
1113
name: string;
Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,89 @@
1-
import { generateKeyPairSync } from 'crypto';
2-
3-
import { PublicKey } from 'aws-cdk-lib/aws-cloudfront';
1+
import { IPublicKey, PublicKey } from 'aws-cdk-lib/aws-cloudfront';
2+
import { Provider } from 'aws-cdk-lib/custom-resources';
3+
import { CfnOutput, CustomResource, RemovalPolicy } from 'aws-cdk-lib';
4+
import { InlineCode, Function, Runtime } from 'aws-cdk-lib/aws-lambda';
45

56
import { type DotStack } from '../constructs/Stack';
67

78
import { addSecret } from './secret';
89
import { addParam, getParamValue } from './ssm';
910

10-
const generateRsaKeyPair = () => {
11-
const { privateKey, publicKey } = generateKeyPairSync('rsa', {
12-
modulusLength: 2048,
13-
privateKeyEncoding: {
14-
format: 'pem',
15-
type: 'pkcs8'
16-
},
17-
publicKeyEncoding: {
18-
format: 'pem',
19-
type: 'spki'
20-
}
11+
export const getKeyPair = (scope: DotStack) => {
12+
const keyGenFunction = new Function(scope, 'KeyGen', {
13+
code: new InlineCode(`
14+
exports.handler=async e=>{
15+
if(e.RequestType!='Delete'){
16+
const k=require('crypto').generateKeyPairSync('rsa',{
17+
modulusLength:2048,
18+
privateKeyEncoding:{format:'pem',type:'pkcs8'},
19+
publicKeyEncoding:{format:'pem',type:'spki'}
20+
});
21+
return{Data:{publicKey:k.publicKey,keyPair:JSON.stringify(k)}};
22+
}
23+
return{};
24+
}`),
25+
handler: 'index.handler',
26+
runtime: Runtime.NODEJS_20_X
27+
});
28+
29+
const customResource = new CustomResource(scope, 'KeyGenResource', {
30+
serviceToken: new Provider(scope, 'KeyGenProvider', {
31+
onEventHandler: keyGenFunction
32+
}).serviceToken
2133
});
22-
return { privateKey, publicKey };
34+
35+
return {
36+
keyPair: customResource.getAttString('keyPair'),
37+
publicKey: customResource.getAttString('publicKey')
38+
};
2339
};
2440

2541
export const addSigningKey = async (scope: DotStack) => {
2642
const baseName = 'signing-pubkey';
43+
const publicKeyName = scope.resourceName(baseName);
2744
const paramName = `${scope.ssmPrefix}/id/${baseName}`;
2845
const existingKeyId = await getParamValue(paramName);
46+
const secretName = `${scope.ssmPrefix}/key/signing`;
47+
const keys = getKeyPair(scope);
48+
let publicKey: IPublicKey;
2949

3050
if (existingKeyId) {
31-
return PublicKey.fromPublicKeyId(
51+
publicKey = PublicKey.fromPublicKeyId(
3252
scope,
3353
`PublicKey-fromPublicKeyId-${+new Date()}`,
3454
existingKeyId
35-
);
55+
) as any;
56+
} else {
57+
publicKey = new PublicKey(scope, publicKeyName, {
58+
encodedKey: keys.publicKey,
59+
publicKeyName
60+
});
61+
scope.overrideId(publicKey as any, publicKeyName);
62+
publicKey.applyRemovalPolicy(RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE);
3663
}
3764

38-
// FIXME: We have to not run this for additional deploys to prod
39-
// because for some reason it fails if the public key exists already
40-
// https://github.com/aws/aws-cdk/issues/15301
41-
const keyPair = generateRsaKeyPair();
42-
43-
addSecret({
65+
const { secret: publicKeySecret } = addSecret({
4466
name: `${scope.env}-signing-key-pair`,
4567
scope,
46-
secretName: `${scope.ssmPrefix}/key/signing`,
47-
value: JSON.stringify(keyPair)
68+
secretName,
69+
value: keys.keyPair
4870
});
4971

50-
const publicKeyName = scope.resourceName(baseName);
51-
const cfKey = new PublicKey(scope, publicKeyName, {
52-
encodedKey: keyPair.publicKey,
53-
publicKeyName
54-
});
55-
56-
scope.overrideId(cfKey, publicKeyName);
57-
58-
addParam({
72+
const publicKeyParam = addParam({
5973
id: `${publicKeyName}-id`,
6074
name: paramName,
6175
scope,
62-
value: cfKey.publicKeyId
76+
value: publicKey.publicKeyId
77+
});
78+
79+
// Note: We HAVE to output this, or else CDK will think we're
80+
// not using the result of PublicKey.fromPublicKeyId and will discard it
81+
// which effectively deletes the publicKey that was created in an initial
82+
// deploy (but not in subsequent deploys)
83+
// eslint-disable-next-line no-new
84+
new CfnOutput(scope, 'cloudfrontPublicKeyId', {
85+
value: publicKey.publicKeyId
6386
});
6487

65-
return cfKey;
88+
return { publicKey, publicKeyParam, publicKeySecret };
6689
};

0 commit comments

Comments
 (0)