diff --git a/lib/__tests__/custom-resource-handlers/pgp-secret.test.ts b/lib/__tests__/custom-resource-handlers/pgp-secret.test.ts index 7c863ac8..29590ac9 100644 --- a/lib/__tests__/custom-resource-handlers/pgp-secret.test.ts +++ b/lib/__tests__/custom-resource-handlers/pgp-secret.test.ts @@ -50,7 +50,7 @@ jest.spyOn(fs, 'mkdtemp') const writeFile = fs.writeFile = jest.fn().mockName('fs.writeFile') .mockImplementation((_pth, _data, _opts, cb) => cb()) as any; jest.mock('../../custom-resource-handlers/src/_exec', () => async (cmd: string, ...args: string[]) => { - await expect(cmd).toBe('/opt/gpg'); + await expect(cmd).toBe('gpg'); const process = require('process'); await expect(process.env.GNUPGHOME).toBe(mockTmpDir); await expect(args).toContain('--batch'); diff --git a/lib/change-controller.ts b/lib/change-controller.ts index fc480487..91faf74b 100644 --- a/lib/change-controller.ts +++ b/lib/change-controller.ts @@ -79,7 +79,7 @@ export class ChangeController extends Construct { const fn = new nodejs.NodejsFunction(this, 'Function', { description: `Enforces a Change Control Policy into CodePipeline's ${props.pipelineStage.stageName} stage`, entry: path.join(__dirname, 'change-control-lambda', 'index.ts'), - runtime: lambda.Runtime.NODEJS_14_X, + runtime: lambda.Runtime.NODEJS_20_X, environment: { // CAPITAL punishment 👌🏻 CHANGE_CONTROL_BUCKET_NAME: changeControlBucket.bucketName, diff --git a/lib/chime-notifier/chime-notifier.ts b/lib/chime-notifier/chime-notifier.ts index 2bbe0888..d6b66928 100644 --- a/lib/chime-notifier/chime-notifier.ts +++ b/lib/chime-notifier/chime-notifier.ts @@ -56,7 +56,7 @@ export class ChimeNotifier extends Construct { handler: 'index.handler', uuid: '0f4a3ee0-692e-4249-932f-a46a833886d8', code: lambda.Code.fromAsset(path.join(__dirname, 'handler')), - runtime: lambda.Runtime.NODEJS_14_X, + runtime: lambda.Runtime.NODEJS_20_X, timeout: Duration.minutes(5), }); diff --git a/lib/code-signing/certificate-signing-request.ts b/lib/code-signing/certificate-signing-request.ts index 3ff72450..cd54de86 100644 --- a/lib/code-signing/certificate-signing-request.ts +++ b/lib/code-signing/certificate-signing-request.ts @@ -66,13 +66,13 @@ export class CertificateSigningRequest extends Construct { uuid: '541F6782-6DCF-49A7-8C5A-67715ADD9E4C', lambdaPurpose: 'CreateCSR', description: 'Creates a Certificate Signing Request document for an x509 certificate', - runtime: lambda.Runtime.NODEJS_14_X, + runtime: lambda.Runtime.NODEJS_20_X, handler: 'index.handler', code: new lambda.AssetCode(codeLocation), timeout: Duration.seconds(300), // add the layer that contains the OpenSSL CLI binary layers: [new lambda.LayerVersion(this, 'OpenSslCliLayer', { - code: lambda.Code.fromAsset(path.join(__dirname, '..', 'custom-resource-handlers', 'layers', 'openssl-cli-layer.zip')), + code: lambda.Code.fromAsset(path.join(__dirname, '..', 'custom-resource-handlers', 'layers', 'openssl-cli-al2023.zip')), })], }); diff --git a/lib/code-signing/private-key.ts b/lib/code-signing/private-key.ts index 032a1e8f..1e6afcb7 100644 --- a/lib/code-signing/private-key.ts +++ b/lib/code-signing/private-key.ts @@ -67,13 +67,13 @@ export class RsaPrivateKeySecret extends Construct { lambdaPurpose: 'RSAPrivate-Key', uuid: '72FD327D-3813-4632-9340-28EC437AA486', description: 'Generates an RSA Private Key and stores it in AWS Secrets Manager', - runtime: lambda.Runtime.NODEJS_14_X, + runtime: lambda.Runtime.NODEJS_20_X, handler: 'index.handler', code: new lambda.AssetCode(codeLocation), timeout: Duration.seconds(300), // add the layer that contains the OpenSSL CLI binary layers: [new lambda.LayerVersion(this, 'OpenSslCliLayer', { - code: lambda.Code.fromAsset(path.join(__dirname, '..', 'custom-resource-handlers', 'layers', 'openssl-cli-layer.zip')), + code: lambda.Code.fromAsset(path.join(__dirname, '..', 'custom-resource-handlers', 'layers', 'openssl-cli-al2023.zip')), })], }); diff --git a/lib/custom-resource-handlers/layers/README.md b/lib/custom-resource-handlers/layers/README.md index 570bc44c..17a37bae 100644 --- a/lib/custom-resource-handlers/layers/README.md +++ b/lib/custom-resource-handlers/layers/README.md @@ -1,11 +1,47 @@ -This directory contains ZIP files that are used as Lambda layers by our custom resources -(private-key, pgp-secret and certificate-signing-request). -Those Lambdas shell out to the `openssl` and `gpg` tools, -which are not shipped with Node Lambda version older than 8. +This directory contains ZIP files that are used as Lambda layers by our custom +resources (private-key, pgp-secret and certificate-signing-request). Those +Lambdas shell out to the following tools: -If you ever need to update these, -unzip these files, add any necessary binaries to it, -and then zip them back up again. +- `gpg` +- `gpg-agent` +- `openssl` + +Only `gpg` is installed on the Lambda Runtime by default, the others are not +(inspect Docker image `public.ecr.aws/lambda/nodejs:20` to be sure). + +If you ever need to update these, unzip these files, add any necessary binaries +to it, and then zip them back up again. The binaries contained in these files were downloaded from an EC2 instance -running Amazon Linux 2. +running Amazon Linux 2023. + +N.B: + +- Make sure the binaries are copied from a version of Amazon Linux that matches + the Lambda Runtime version that is being used, see here: + . +- Make sure that the file structure in the ZIP file does not contain an extra + directory, but looks like: + - `gpg` + - `lib/libgcrypt.so.X` + - etc. +- `gpg` is probably linked against the major version dependencies only, so it will + depend on `libgcrypt.so.8` (and not `libgcrypt.so.8.4.1`). Confirm with `ldd` and + rename the files if necessary. + +# Potential update procedure + +```shell +host$ exec docker run --net=host \ + --rm -it \ + -v $HOME:$HOME -w $PWD \ + public.ecr.aws/amazonlinux/amazonlinux:2023 + +# Replace 'gnupg2-minimal' with 'gnupg2', copy gpg-agent out to the current directory +container$ yum install gnupg2 -y --allowerasing +container$ cp /usr/bin/gpg-agent . + +# Install openssl, copy CLI out +container$ yum install -y openssl +container$ cp /usr/bin/openssl . +``` \ No newline at end of file diff --git a/lib/custom-resource-handlers/layers/gpg-agent-al2023.zip b/lib/custom-resource-handlers/layers/gpg-agent-al2023.zip new file mode 100644 index 00000000..45439290 Binary files /dev/null and b/lib/custom-resource-handlers/layers/gpg-agent-al2023.zip differ diff --git a/lib/custom-resource-handlers/layers/gpg-layer.zip b/lib/custom-resource-handlers/layers/gpg-layer.zip deleted file mode 100644 index 99ebfad4..00000000 Binary files a/lib/custom-resource-handlers/layers/gpg-layer.zip and /dev/null differ diff --git a/lib/custom-resource-handlers/layers/openssl-cli-layer.zip b/lib/custom-resource-handlers/layers/openssl-cli-al2023.zip similarity index 99% rename from lib/custom-resource-handlers/layers/openssl-cli-layer.zip rename to lib/custom-resource-handlers/layers/openssl-cli-al2023.zip index d5390bca..dd66bfab 100644 Binary files a/lib/custom-resource-handlers/layers/openssl-cli-layer.zip and b/lib/custom-resource-handlers/layers/openssl-cli-al2023.zip differ diff --git a/lib/custom-resource-handlers/src/pgp-secret.ts b/lib/custom-resource-handlers/src/pgp-secret.ts index b546457e..14fe3036 100644 --- a/lib/custom-resource-handlers/src/pgp-secret.ts +++ b/lib/custom-resource-handlers/src/pgp-secret.ts @@ -21,6 +21,10 @@ const ssm = new aws.SSM(); exports.handler = cfn.customResourceHandler(handleEvent); +// Used to be /opt/gpg, but now is just plain gpg +const GPG_BIN = 'gpg'; + + interface ResourceAttributes extends cfn.ResourceAttributes { SecretArn: string; PublicKey: string; @@ -88,9 +92,9 @@ async function _createNewKey(event: cfn.CreateEvent | cfn.UpdateEvent, context: ].join('\n'), { encoding: 'utf8' }); const gpgCommonArgs = [`--homedir=${tempDir}`, '--agent-program=/opt/gpg-agent']; - await _exec('/opt/gpg', ...gpgCommonArgs, '--batch', '--gen-key', keyConfig); - const keyMaterial = await _exec('/opt/gpg', ...gpgCommonArgs, '--batch', '--yes', '--export-secret-keys', '--armor'); - const publicKey = await _exec('/opt/gpg', ...gpgCommonArgs, '--batch', '--yes', '--export', '--armor'); + await _exec(GPG_BIN, ...gpgCommonArgs, '--batch', '--gen-key', keyConfig); + const keyMaterial = await _exec(GPG_BIN, ...gpgCommonArgs, '--batch', '--yes', '--export-secret-keys', '--armor'); + const publicKey = await _exec(GPG_BIN, ...gpgCommonArgs, '--batch', '--yes', '--export', '--armor'); const secretOpts = { ClientRequestToken: context.awsRequestId, Description: event.ResourceProperties.Description, @@ -153,8 +157,8 @@ async function _getPublicKey(secretArn: string): Promise { await writeFile(privateKeyFile, keyData.PrivateKey, { encoding: 'utf-8' }); const gpgCommonArgs = [`--homedir=${tempDir}`, '--agent-program=/opt/gpg-agent']; // Note: importing a private key does NOT require entering it's passphrase! - await _exec('/opt/gpg', ...gpgCommonArgs, '--batch', '--yes', '--import', privateKeyFile); - return await _exec('/opt/gpg', ...gpgCommonArgs, '--batch', '--yes', '--export', '--armor'); + await _exec(GPG_BIN, ...gpgCommonArgs, '--batch', '--yes', '--import', privateKeyFile); + return await _exec(GPG_BIN, ...gpgCommonArgs, '--batch', '--yes', '--export', '--armor'); } finally { await _rmrf(tempDir); } diff --git a/lib/open-pgp-key-pair.ts b/lib/open-pgp-key-pair.ts index 2a0ae30a..f05f87ac 100644 --- a/lib/open-pgp-key-pair.ts +++ b/lib/open-pgp-key-pair.ts @@ -120,10 +120,10 @@ export class OpenPGPKeyPair extends Construct implements ICredentialPair { code: new lambda.AssetCode(codeLocation), handler: 'index.handler', timeout: Duration.seconds(300), - runtime: lambda.Runtime.NODEJS_14_X, - // add the layer that contains the GPG binary (+ shared libraries) + runtime: lambda.Runtime.NODEJS_20_X, + // add the layer that contains the gpg-agent binary (gpg command itself is already on the host) layers: [new lambda.LayerVersion(this, 'GpgLayer', { - code: lambda.Code.fromAsset(path.join(__dirname, 'custom-resource-handlers', 'layers', 'gpg-layer.zip')), + code: lambda.Code.fromAsset(path.join(__dirname, 'custom-resource-handlers', 'layers', 'gpg-agent-al2023.zip')), })], }); diff --git a/lib/pipeline-watcher/watcher.ts b/lib/pipeline-watcher/watcher.ts index ed88a0c1..f9aeba70 100644 --- a/lib/pipeline-watcher/watcher.ts +++ b/lib/pipeline-watcher/watcher.ts @@ -53,7 +53,7 @@ export class PipelineWatcher extends Construct { const pipelineWatcher = new lambda.Function(this, 'Poller', { handler: 'watcher-handler.handler', - runtime: lambda.Runtime.NODEJS_14_X, + runtime: lambda.Runtime.NODEJS_20_X, code: lambda.Code.fromAsset(path.join(__dirname, 'handler')), environment: { METRIC_NAMESPACE: props.metricNamespace,