From b1db20ff58592235f2dfc508455ad187d1c8ef42 Mon Sep 17 00:00:00 2001 From: Pete F Date: Fri, 26 Jun 2026 16:38:10 +0100 Subject: [PATCH] Initial attempt at developer policy for local running --- cdk/bin/cdk.ts | 1 - cdk/lib/__snapshots__/newswires.test.ts.snap | 279 +++++++++++++++ .../__snapshots__/whole-stack.test.ts.snap | 279 +++++++++++++++ cdk/lib/local-run-developer-policy.ts | 209 +++++++++++ cdk/lib/newswires.ts | 39 ++- cdk/package.json | 2 +- package-lock.json | 326 +++++++----------- 7 files changed, 920 insertions(+), 215 deletions(-) create mode 100644 cdk/lib/local-run-developer-policy.ts diff --git a/cdk/bin/cdk.ts b/cdk/bin/cdk.ts index c4cd93c9..18e14fa0 100644 --- a/cdk/bin/cdk.ts +++ b/cdk/bin/cdk.ts @@ -1,7 +1,6 @@ import { RiffRaffYamlFile } from '@guardian/cdk/lib/riff-raff-yaml-file'; import { UnknownRiffRaffProjectName } from '@guardian/cdk/lib/riff-raff-yaml-file/types'; import { App } from 'aws-cdk-lib'; -import 'source-map-support/register'; import { STACK } from 'newswires-shared/constants'; import type { PollerId } from 'newswires-shared/pollers'; import { diff --git a/cdk/lib/__snapshots__/newswires.test.ts.snap b/cdk/lib/__snapshots__/newswires.test.ts.snap index 7fdeda77..403fc4f6 100644 --- a/cdk/lib/__snapshots__/newswires.test.ts.snap +++ b/cdk/lib/__snapshots__/newswires.test.ts.snap @@ -64,6 +64,7 @@ exports[`The Newswires stack > matches the snapshot 1`] = ` "GuUnhealthyInstancesAlarm", "GuS3Bucket", "GuCname", + "GuDeveloperPolicyExperimental", "GuAllowPolicy", "GuGithubActionsRole", ], @@ -2783,6 +2784,284 @@ exports[`The Newswires stack > matches the snapshot 1`] = ` }, "Type": "AWS::CloudWatch::Alarm", }, + "NewswiresCodeLocalRunPolicy703D2604": { + "Properties": { + "Description": "Run Newswires locally against CODE DB", + "Path": "/developer-policy/guardian/newswires/editorial-feeds/TEST/newswires-local-run-against-code-db/", + "PolicyDocument": { + "Statement": [ + { + "Action": "ssm:GetParameter", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ssm:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":parameter/CODE/editorial-feeds/newswires/database/*", + ], + ], + }, + }, + { + "Action": [ + "ec2:DescribeInstances", + "rds:DescribeDBInstances", + "ssm:GetCommandInvocation", + "ssm:GetConnectionStatus", + "ssm:DescribeSessions", + ], + "Condition": { + "StringEquals": { + "aws:RequestedRegion": "eu-west-1", + }, + }, + "Effect": "Allow", + "Resource": "*", + }, + { + "Action": "ssm:SendCommand", + "Condition": { + "StringEquals": { + "ssm:resourceTag/App": "newswires", + "ssm:resourceTag/Stack": "editorial-feeds", + "ssm:resourceTag/Stage": "CODE", + }, + }, + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ec2:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":instance/*", + ], + ], + }, + }, + { + "Action": "ssm:SendCommand", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ssm:eu-west-1::document/AWS-RunShellScript", + ], + ], + }, + }, + { + "Action": "ssm:StartSession", + "Condition": { + "StringEquals": { + "ssm:resourceTag/App": "newswires", + "ssm:resourceTag/Stack": "editorial-feeds", + "ssm:resourceTag/Stage": "CODE", + }, + }, + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ec2:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":instance/*", + ], + ], + }, + }, + { + "Action": "ssm:StartSession", + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ssm:eu-west-1::document/AWS-StartPortForwardingSessionToRemoteHost", + ], + ], + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ssm:eu-west-1::document/AWS-StartSSHSession", + ], + ], + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ssm:eu-west-1::document/SSM-SessionManagerRunShell", + ], + ], + }, + ], + }, + { + "Action": [ + "ssm:TerminateSession", + "ssm:ResumeSession", + ], + "Condition": { + "StringEquals": { + "aws:RequestedRegion": "eu-west-1", + }, + }, + "Effect": "Allow", + "Resource": "*", + }, + { + "Action": "ec2:CreateTags", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ec2:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":instance/*", + ], + ], + }, + }, + { + "Action": "rds-db:connect", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":rds-db:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":dbuser:", + { + "Fn::GetAtt": [ + "NewswiresDBNewswires2A5A9AC6", + "DbiResourceId", + ], + }, + "/postgres", + ], + ], + }, + }, + { + "Action": "s3:GetObject", + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "PanDomainSettingsBucket", + }, + "/local.dev-gutools.co.uk.settings", + ], + ], + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "PanDomainSettingsBucket", + }, + "/local.dev-gutools.co.uk.settings.public", + ], + ], + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "PanDomainSettingsBucket", + }, + "/pan-domain-auth-*.p12", + ], + ], + }, + ], + }, + { + "Action": "s3:GetObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "PermissionsBucket", + }, + "/CODE/permissions.json", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::ManagedPolicy", + }, "NewswiresCopyEmailBucketNewswires26C541A0": { "DeletionPolicy": "Retain", "Properties": { diff --git a/cdk/lib/__snapshots__/whole-stack.test.ts.snap b/cdk/lib/__snapshots__/whole-stack.test.ts.snap index 7a859e8a..ef87dfa1 100644 --- a/cdk/lib/__snapshots__/whole-stack.test.ts.snap +++ b/cdk/lib/__snapshots__/whole-stack.test.ts.snap @@ -1077,6 +1077,7 @@ exports[`createStacks > should create WiresFeeds and Newswires stacks with corre "GuUnhealthyInstancesAlarm", "GuS3Bucket", "GuCname", + "GuDeveloperPolicyExperimental", "GuAllowPolicy", "GuGithubActionsRole", ], @@ -3796,6 +3797,284 @@ exports[`createStacks > should create WiresFeeds and Newswires stacks with corre }, "Type": "AWS::CloudWatch::Alarm", }, + "NewswiresCodeLocalRunPolicy703D2604": { + "Properties": { + "Description": "Run Newswires locally against CODE DB", + "Path": "/developer-policy/guardian/newswires/editorial-feeds/TEST/newswires-local-run-against-code-db/", + "PolicyDocument": { + "Statement": [ + { + "Action": "ssm:GetParameter", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ssm:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":parameter/CODE/editorial-feeds/newswires/database/*", + ], + ], + }, + }, + { + "Action": [ + "ec2:DescribeInstances", + "rds:DescribeDBInstances", + "ssm:GetCommandInvocation", + "ssm:GetConnectionStatus", + "ssm:DescribeSessions", + ], + "Condition": { + "StringEquals": { + "aws:RequestedRegion": "eu-west-1", + }, + }, + "Effect": "Allow", + "Resource": "*", + }, + { + "Action": "ssm:SendCommand", + "Condition": { + "StringEquals": { + "ssm:resourceTag/App": "newswires", + "ssm:resourceTag/Stack": "editorial-feeds", + "ssm:resourceTag/Stage": "CODE", + }, + }, + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ec2:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":instance/*", + ], + ], + }, + }, + { + "Action": "ssm:SendCommand", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ssm:eu-west-1::document/AWS-RunShellScript", + ], + ], + }, + }, + { + "Action": "ssm:StartSession", + "Condition": { + "StringEquals": { + "ssm:resourceTag/App": "newswires", + "ssm:resourceTag/Stack": "editorial-feeds", + "ssm:resourceTag/Stage": "CODE", + }, + }, + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ec2:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":instance/*", + ], + ], + }, + }, + { + "Action": "ssm:StartSession", + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ssm:eu-west-1::document/AWS-StartPortForwardingSessionToRemoteHost", + ], + ], + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ssm:eu-west-1::document/AWS-StartSSHSession", + ], + ], + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ssm:eu-west-1::document/SSM-SessionManagerRunShell", + ], + ], + }, + ], + }, + { + "Action": [ + "ssm:TerminateSession", + "ssm:ResumeSession", + ], + "Condition": { + "StringEquals": { + "aws:RequestedRegion": "eu-west-1", + }, + }, + "Effect": "Allow", + "Resource": "*", + }, + { + "Action": "ec2:CreateTags", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ec2:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":instance/*", + ], + ], + }, + }, + { + "Action": "rds-db:connect", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":rds-db:eu-west-1:", + { + "Ref": "AWS::AccountId", + }, + ":dbuser:", + { + "Fn::GetAtt": [ + "NewswiresDBNewswires2A5A9AC6", + "DbiResourceId", + ], + }, + "/postgres", + ], + ], + }, + }, + { + "Action": "s3:GetObject", + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "PanDomainSettingsBucket", + }, + "/local.dev-gutools.co.uk.settings", + ], + ], + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "PanDomainSettingsBucket", + }, + "/local.dev-gutools.co.uk.settings.public", + ], + ], + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "PanDomainSettingsBucket", + }, + "/pan-domain-auth-*.p12", + ], + ], + }, + ], + }, + { + "Action": "s3:GetObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "PermissionsBucket", + }, + "/CODE/permissions.json", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::ManagedPolicy", + }, "NewswiresCopyEmailBucketNewswires26C541A0": { "DeletionPolicy": "Retain", "Properties": { diff --git a/cdk/lib/local-run-developer-policy.ts b/cdk/lib/local-run-developer-policy.ts new file mode 100644 index 00000000..3b901849 --- /dev/null +++ b/cdk/lib/local-run-developer-policy.ts @@ -0,0 +1,209 @@ +import type { GuStack } from '@guardian/cdk/lib/constructs/core'; +import { GuDeveloperPolicyExperimental } from '@guardian/cdk/lib/experimental/constructs/iam/policies'; +import { ArnFormat } from 'aws-cdk-lib'; +import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam'; +import type { GuDatabase } from './constructs/database'; + +function ssmArn(stack: GuStack, parameterName: string): string { + return stack.formatArn({ + service: 'ssm', + resource: 'parameter', + /* Strip any leading slash because formatArn already inserts a separator between + resource and resourceName so a leading slash would produce a double slash. */ + resourceName: parameterName.replace(/^\//, ''), + }); +} + +export function createLocalRunDeveloperPolicy( + scope: GuStack, + stack: string, + app: string, + db: GuDatabase, + panDomainSettingsBucketName: string, + permissionsBucketName: string, +) { + const stage = 'CODE'; + const region = scope.region; + + /** + * SSM documents used by ssm-scala's `--rds-tunnel` transport. These are + * AWS-owned public documents, whose ARNs have an *empty* account field + * (e.g. `arn:aws:ssm:eu-west-1::document/AWS-RunShellScript`). We keep the + * region pinned to the stack region but must leave the account blank, + * otherwise the resource never matches and the action is denied. + * https://docs.aws.amazon.com/systems-manager/latest/userguide/documents.html + */ + function ssmDocumentArn(name: string) { + return scope.formatArn({ + service: 'ssm', + region, + account: '', + resource: 'document', + resourceName: name, + }); + } + /** + * The tags that we use to specify the Newswires CODE EC2 instances when + * connecting via ssm-scala's `--rds-tunnel` transport (`-t newswires,CODE`). + */ + const newswiresInstanceTagConditions = { + StringEquals: { + 'ssm:resourceTag/App': app, + 'ssm:resourceTag/Stack': stack, + 'ssm:resourceTag/Stage': stage, + }, + }; + + const newswiresInstanceArn = scope.formatArn({ + service: 'ec2', + resource: 'instance', + resourceName: '*', + }); + + new GuDeveloperPolicyExperimental(scope, 'NewswiresCodeLocalRunPolicy', { + grantId: 'newswires-local-run-against-code-db', + friendlyName: 'Run Newswires locally against CODE DB', + withoutPolicyChecks: true, + statements: [ + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['ssm:GetParameter'], + resources: [ssmArn(scope, `${stage}/${stack}/${app}/database/*`)], + }), + /** + * Purpose — account-wide discovery / status actions. + * Rationale for scope: AWS does not support resource-level + * permissions for these, so they can only be granted on `*`. We + * narrow them instead with a region condition, since all of the + * relevant resources are in eu-west-1. + */ + new PolicyStatement({ + effect: Effect.ALLOW, + actions: [ + 'ec2:DescribeInstances', + 'rds:DescribeDBInstances', + 'ssm:GetCommandInvocation', + 'ssm:GetConnectionStatus', + 'ssm:DescribeSessions', + ], + resources: ['*'], + conditions: { + StringEquals: { 'aws:RequestedRegion': region }, + }, + }), + // Tier 2 — instance-targeted actions, scoped to the Newswires CODE + // instances by tag. SendCommand needs both the target instance(s) and + // the document; a tag condition can't be applied to the document (it + // has no such tags), so the instance and document grants are split + // into separate statements — the standard AWS pattern. + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['ssm:SendCommand'], + resources: [newswiresInstanceArn], + conditions: newswiresInstanceTagConditions, + }), + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['ssm:SendCommand'], + resources: [ssmDocumentArn('AWS-RunShellScript')], + }), + // StartSession, likewise split: the tagged instance(s) plus the + // session-manager documents ssm-scala uses for the tunnel. + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['ssm:StartSession'], + resources: [newswiresInstanceArn], + conditions: newswiresInstanceTagConditions, + }), + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['ssm:StartSession'], + resources: [ + ssmDocumentArn('AWS-StartPortForwardingSessionToRemoteHost'), + ssmDocumentArn('AWS-StartSSHSession'), + ssmDocumentArn('SSM-SessionManagerRunShell'), + ], + }), + /** + * Purpose: Let a developer resume / close sessions. + * Rationale for scope: Ideally this would be + * scoped to the caller's own sessions (`session/${aws:username}-*`), + * but developers reach CODE via Janus assumed-role credentials, for + * which `${aws:username}` is empty and `${aws:userid}` (role-id: + * session-name) does not match the `-` session + * ARN. There is no policy variable that reconstructs the session id + * under federated access, so — as Guardian's own Janus `ssm-sessions` + * policy does — we grant these on all sessions, bounded by region. + */ + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['ssm:TerminateSession', 'ssm:ResumeSession'], + resources: ['*'], + conditions: { + StringEquals: { 'aws:RequestedRegion': region }, + }, + }), + /** + * ssm-scala tags the instance ('tainted') to record who accessed it. + * Can potentially be removed once ssm-scala is deprecated. + * */ + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['ec2:CreateTags'], + resources: [ + scope.formatArn({ + service: 'ec2', + resource: 'instance', + resourceName: '*', + }), + ], + }), + /** + * Purpose: Allow the developer to connect to the CODE RDS instance + * Rationale for scope: The RDS DB user ARN is unique to the instance + * and the user, so this is already scoped to the correct instance. + * It might be good to create a separate, non-root db user for developers + * but we don't have one at the time of writing. + */ + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['rds-db:connect'], + resources: [ + scope.formatArn({ + arnFormat: ArnFormat.COLON_RESOURCE_NAME, + service: 'rds-db', + resource: 'dbuser', + resourceName: `${db.instanceResourceId}/postgres`, + }), + ], + }), + /** + * Logging in locally: the app reads pan-domain auth settings and + * permissions data from S3. + * Mirrors the GuGetS3ObjectsPolicy grants on the deployed app role. + */ + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['s3:GetObject'], + resources: [ + `arn:aws:s3:::${panDomainSettingsBucketName}/local.dev-gutools.co.uk.settings`, + `arn:aws:s3:::${panDomainSettingsBucketName}/local.dev-gutools.co.uk.settings.public`, + /** + * The exact object key for this certificate is only found at + * runtime so we can't specify it exactly here. The certificate + * is not stage-specific either so we can't restrict to a given stage + * in the way we are doing for other objects. + */ + `arn:aws:s3:::${panDomainSettingsBucketName}/pan-domain-auth-*.p12`, + ], + }), + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['s3:GetObject'], + resources: [ + `arn:aws:s3:::${permissionsBucketName}/${stage}/permissions.json`, + ], + }), + ], + }); +} diff --git a/cdk/lib/newswires.ts b/cdk/lib/newswires.ts index 6d007b26..d069b8be 100644 --- a/cdk/lib/newswires.ts +++ b/cdk/lib/newswires.ts @@ -71,6 +71,7 @@ import { POLLERS_CONFIG } from 'newswires-shared/pollers'; import { appName, LAMBDA_ARCHITECTURE, LAMBDA_RUNTIME } from './constants'; import { GuDatabase } from './constructs/database'; import { PollerLambda } from './constructs/pollerLambda'; +import { createLocalRunDeveloperPolicy } from './local-run-developer-policy'; export type NewswiresProps = GuStackProps & { sourceQueue: Queue; @@ -83,7 +84,10 @@ export type NewswiresProps = GuStackProps & { export class Newswires extends GuStack { constructor(scope: App, id: string, props: NewswiresProps) { - super(scope, id, { ...props, app: appName }); + super(scope, id, { + ...props, + app: appName, + }); const certificateArn = new GuParameter(this, 'CloudFrontCertificateArn', { description: `The ARN of the CloudFront certificate for ${props.domainName}, for consumption by the Newswires app stack.`, @@ -514,17 +518,15 @@ export class Newswires extends GuStack { scaling, applicationLogging: { enabled: true, systemdUnitName: appName }, imageRecipe: 'editorial-tools-jammy-java21', - roleConfiguration: { - additionalPolicies: [ - new GuGetS3ObjectsPolicy(this, 'PandaAuthPolicy', { - bucketName: panDomainSettingsBucket.valueAsString, - }), - new GuGetS3ObjectsPolicy(this, 'PermissionsCachePolicy', { - bucketName: permissionsBucketName.valueAsString, - paths: [`${this.stage}/*`], - }), - ], - }, + additionalPolicies: [ + new GuGetS3ObjectsPolicy(this, 'PandaAuthPolicy', { + bucketName: panDomainSettingsBucket.valueAsString, + }), + new GuGetS3ObjectsPolicy(this, 'PermissionsCachePolicy', { + bucketName: permissionsBucketName.valueAsString, + paths: [`${this.stage}/*`], + }), + ], instanceMetricGranularity: this.stage === 'PROD' ? '1Minute' : '5Minute', }); @@ -579,6 +581,19 @@ export class Newswires extends GuStack { database.accessSecurityGroup, ); + // Developer policy allowing engineers to run the app locally against the + // CODE database (./scripts/start --use-CODE). No-op outside the CODE stack (and included in TEST so we get the snapshot). + if (this.stage === 'CODE' || this.stage === 'TEST') { + createLocalRunDeveloperPolicy( + this, + this.stack, + appName, + database, + panDomainSettingsBucket.valueAsString, + permissionsBucketName.valueAsString, + ); + } + if (this.stage === 'PROD' || this.stage === 'TEST') { const param = new StringParameter( this, diff --git a/cdk/package.json b/cdk/package.json index 9f6f4645..3a62d9a8 100644 --- a/cdk/package.json +++ b/cdk/package.json @@ -13,7 +13,7 @@ "lint:ci": "eslint lib/** bin/** --no-error-on-unmatched-pattern" }, "devDependencies": { - "@guardian/cdk": "63.6.1", + "@guardian/cdk": "64.0.0", "@guardian/prettier": "^8.0.1", "newswires-shared": "*", "prettier": "^3.6.2", diff --git a/package-lock.json b/package-lock.json index 42af5e80..9fb642b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,7 +48,7 @@ "cdk": { "version": "0.0.0", "devDependencies": { - "@guardian/cdk": "63.6.1", + "@guardian/cdk": "64.0.0", "@guardian/prettier": "^8.0.1", "newswires-shared": "*", "prettier": "^3.6.2", @@ -56,6 +56,31 @@ "ts-node": "^10.9.2" } }, + "cdk/node_modules/@guardian/cdk": { + "version": "64.0.0", + "resolved": "https://registry.npmjs.org/@guardian/cdk/-/cdk-64.0.0.tgz", + "integrity": "sha512-DWDV+P81uRi9kPO4gw+9jmBF3fYsE4LA5Q000Q4aYTG0TM+THOeJMsp49clJRTLkNCDIbXzzw7uMRD0OpAlSqQ==", + "dev": true, + "dependencies": { + "@aws-sdk/client-ec2": "^3.1034.0", + "@aws-sdk/client-ssm": "^3.1034.0", + "@aws-sdk/credential-providers": "^3.1034.0", + "chalk": "^4.1.2", + "codemaker": "^1.128.0", + "git-url-parse": "^16.0.1", + "js-yaml": "^4.1.1", + "read-pkg-up": "7.0.1", + "yargs": "^17.7.2" + }, + "bin": { + "gu-cdk": "bin/gu-cdk" + }, + "peerDependencies": { + "aws-cdk": "^2.1110.0", + "aws-cdk-lib": "^2.241.0", + "constructs": "^10.5.1" + } + }, "cdk/node_modules/@guardian/prettier": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@guardian/prettier/-/prettier-8.0.2.tgz", @@ -1665,79 +1690,27 @@ } }, "node_modules/@aws-sdk/credential-providers": { - "version": "3.986.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.986.0.tgz", - "integrity": "sha512-3v4xBiZNTanMuYK9t6AP7rtue9zYkO60wXw2DqbksDc+ibyZicq8t8MgN6x796QCTXXowbOrM1U3F0jMkkP/ww==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-cognito-identity": "3.986.0", - "@aws-sdk/core": "^3.973.7", - "@aws-sdk/credential-provider-cognito-identity": "^3.972.3", - "@aws-sdk/credential-provider-env": "^3.972.5", - "@aws-sdk/credential-provider-http": "^3.972.7", - "@aws-sdk/credential-provider-ini": "^3.972.5", - "@aws-sdk/credential-provider-login": "^3.972.5", - "@aws-sdk/credential-provider-node": "^3.972.6", - "@aws-sdk/credential-provider-process": "^3.972.5", - "@aws-sdk/credential-provider-sso": "^3.972.5", - "@aws-sdk/credential-provider-web-identity": "^3.972.5", - "@aws-sdk/nested-clients": "3.986.0", - "@aws-sdk/types": "^3.973.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.1", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/nested-clients": { - "version": "3.986.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.986.0.tgz", - "integrity": "sha512-/yq4hr3KUBIIX/bcccscXOzFoe6NSiAUFTsHaM2VZWYpPw7JwlqnPsfFVONAjuuYovjP/O+qYBx1oj85C7Dplw==", + "version": "3.1088.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.1088.0.tgz", + "integrity": "sha512-PUlCtB3u7bg/IJmS1jihqqLDBAeZU48OQ9lBg5IW1+tGOVlQ+zqxAFSSryqynKPC5bYlau5tO3qskl/oD8K2MA==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.7", - "@aws-sdk/middleware-host-header": "^3.972.3", - "@aws-sdk/middleware-logger": "^3.972.3", - "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.7", - "@aws-sdk/region-config-resolver": "^3.972.3", - "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.986.0", - "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.5", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.1", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.13", - "@smithy/middleware-retry": "^4.4.30", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.9", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.2", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.29", - "@smithy/util-defaults-mode-node": "^4.2.32", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/core": "^3.975.3", + "@aws-sdk/credential-provider-cognito-identity": "^3.972.58", + "@aws-sdk/credential-provider-env": "^3.972.59", + "@aws-sdk/credential-provider-http": "^3.972.61", + "@aws-sdk/credential-provider-ini": "^3.973.3", + "@aws-sdk/credential-provider-login": "^3.972.65", + "@aws-sdk/credential-provider-node": "^3.972.69", + "@aws-sdk/credential-provider-process": "^3.972.59", + "@aws-sdk/credential-provider-sso": "^3.973.3", + "@aws-sdk/credential-provider-web-identity": "^3.972.65", + "@aws-sdk/nested-clients": "^3.997.33", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.29.4", + "@smithy/credential-provider-imds": "^4.4.9", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -2027,6 +2000,86 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/rds-signer/node_modules/@aws-sdk/credential-providers": { + "version": "3.986.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.986.0.tgz", + "integrity": "sha512-3v4xBiZNTanMuYK9t6AP7rtue9zYkO60wXw2DqbksDc+ibyZicq8t8MgN6x796QCTXXowbOrM1U3F0jMkkP/ww==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.986.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/credential-provider-cognito-identity": "^3.972.3", + "@aws-sdk/credential-provider-env": "^3.972.5", + "@aws-sdk/credential-provider-http": "^3.972.7", + "@aws-sdk/credential-provider-ini": "^3.972.5", + "@aws-sdk/credential-provider-login": "^3.972.5", + "@aws-sdk/credential-provider-node": "^3.972.6", + "@aws-sdk/credential-provider-process": "^3.972.5", + "@aws-sdk/credential-provider-sso": "^3.972.5", + "@aws-sdk/credential-provider-web-identity": "^3.972.5", + "@aws-sdk/nested-clients": "3.986.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.1", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/rds-signer/node_modules/@aws-sdk/nested-clients": { + "version": "3.986.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.986.0.tgz", + "integrity": "sha512-/yq4hr3KUBIIX/bcccscXOzFoe6NSiAUFTsHaM2VZWYpPw7JwlqnPsfFVONAjuuYovjP/O+qYBx1oj85C7Dplw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.7", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.7", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.986.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.5", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.13", + "@smithy/middleware-retry": "^4.4.30", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.29", + "@smithy/util-defaults-mode-node": "^4.2.32", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/region-config-resolver": { "version": "3.972.3", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz", @@ -3574,59 +3627,6 @@ } } }, - "node_modules/@guardian/cdk": { - "version": "63.6.1", - "resolved": "https://registry.npmjs.org/@guardian/cdk/-/cdk-63.6.1.tgz", - "integrity": "sha512-C9uiYwGnPxIOSOw3Gn3DTuoK9u85W29aggFgAE/oG9wt3TP7UGOyydhjq+lEmuYqgwFE0zgh4uWbldwG3IfVyQ==", - "dev": true, - "dependencies": { - "@aws-sdk/client-ec2": "^3.1034.0", - "@aws-sdk/client-ssm": "^3.1034.0", - "@aws-sdk/credential-providers": "^3.1034.0", - "chalk": "^4.1.2", - "codemaker": "^1.128.0", - "git-url-parse": "^16.0.1", - "js-yaml": "^4.1.1", - "read-pkg-up": "7.0.1", - "yargs": "^17.7.2" - }, - "bin": { - "gu-cdk": "bin/gu-cdk" - }, - "peerDependencies": { - "aws-cdk": "^2.1110.0", - "aws-cdk-lib": "^2.241.0", - "constructs": "^10.5.1" - } - }, - "node_modules/@guardian/cdk/node_modules/@aws-sdk/credential-providers": { - "version": "3.1088.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.1088.0.tgz", - "integrity": "sha512-PUlCtB3u7bg/IJmS1jihqqLDBAeZU48OQ9lBg5IW1+tGOVlQ+zqxAFSSryqynKPC5bYlau5tO3qskl/oD8K2MA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.975.3", - "@aws-sdk/credential-provider-cognito-identity": "^3.972.58", - "@aws-sdk/credential-provider-env": "^3.972.59", - "@aws-sdk/credential-provider-http": "^3.972.61", - "@aws-sdk/credential-provider-ini": "^3.973.3", - "@aws-sdk/credential-provider-login": "^3.972.65", - "@aws-sdk/credential-provider-node": "^3.972.69", - "@aws-sdk/credential-provider-process": "^3.972.59", - "@aws-sdk/credential-provider-sso": "^3.973.3", - "@aws-sdk/credential-provider-web-identity": "^3.972.65", - "@aws-sdk/nested-clients": "^3.997.33", - "@aws-sdk/types": "^3.974.2", - "@smithy/core": "^3.29.4", - "@smithy/credential-provider-imds": "^4.4.9", - "@smithy/types": "^4.16.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@guardian/eslint-config": { "version": "13.0.5", "resolved": "https://registry.npmjs.org/@guardian/eslint-config/-/eslint-config-13.0.5.tgz", @@ -12227,16 +12227,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -13035,20 +13025,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/read-pkg-up/node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -13056,19 +13032,6 @@ "dev": true, "license": "ISC" }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/read-pkg-up/node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -13082,35 +13045,6 @@ "validate-npm-package-license": "^3.0.1" } }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/read-pkg-up/node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -13147,16 +13081,6 @@ "semver": "bin/semver" } }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, "node_modules/read-pkg/node_modules/parse-json": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz",