|
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'; |
4 | 5 |
|
5 | 6 | import { type DotStack } from '../constructs/Stack'; |
6 | 7 |
|
7 | 8 | import { addSecret } from './secret'; |
8 | 9 | import { addParam, getParamValue } from './ssm'; |
9 | 10 |
|
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 |
21 | 33 | }); |
22 | | - return { privateKey, publicKey }; |
| 34 | + |
| 35 | + return { |
| 36 | + keyPair: customResource.getAttString('keyPair'), |
| 37 | + publicKey: customResource.getAttString('publicKey') |
| 38 | + }; |
23 | 39 | }; |
24 | 40 |
|
25 | 41 | export const addSigningKey = async (scope: DotStack) => { |
26 | 42 | const baseName = 'signing-pubkey'; |
| 43 | + const publicKeyName = scope.resourceName(baseName); |
27 | 44 | const paramName = `${scope.ssmPrefix}/id/${baseName}`; |
28 | 45 | const existingKeyId = await getParamValue(paramName); |
| 46 | + const secretName = `${scope.ssmPrefix}/key/signing`; |
| 47 | + const keys = getKeyPair(scope); |
| 48 | + let publicKey: IPublicKey; |
29 | 49 |
|
30 | 50 | if (existingKeyId) { |
31 | | - return PublicKey.fromPublicKeyId( |
| 51 | + publicKey = PublicKey.fromPublicKeyId( |
32 | 52 | scope, |
33 | 53 | `PublicKey-fromPublicKeyId-${+new Date()}`, |
34 | 54 | 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); |
36 | 63 | } |
37 | 64 |
|
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({ |
44 | 66 | name: `${scope.env}-signing-key-pair`, |
45 | 67 | scope, |
46 | | - secretName: `${scope.ssmPrefix}/key/signing`, |
47 | | - value: JSON.stringify(keyPair) |
| 68 | + secretName, |
| 69 | + value: keys.keyPair |
48 | 70 | }); |
49 | 71 |
|
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({ |
59 | 73 | id: `${publicKeyName}-id`, |
60 | 74 | name: paramName, |
61 | 75 | 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 |
63 | 86 | }); |
64 | 87 |
|
65 | | - return cfKey; |
| 88 | + return { publicKey, publicKeyParam, publicKeySecret }; |
66 | 89 | }; |
0 commit comments