diff --git a/.tool-versions b/.tool-versions index 87e4aa80..e362d7a3 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1,2 @@ java corretto-21.0.9.10.1 +nodejs 22.15.0 diff --git a/cdk/lib/__snapshots__/newswires.test.ts.snap b/cdk/lib/__snapshots__/newswires.test.ts.snap index 403fc4f6..2431fb41 100644 --- a/cdk/lib/__snapshots__/newswires.test.ts.snap +++ b/cdk/lib/__snapshots__/newswires.test.ts.snap @@ -2939,10 +2939,20 @@ exports[`The Newswires stack > matches the snapshot 1`] = ` ], }, { - "Action": [ - "ssm:TerminateSession", - "ssm:ResumeSession", - ], + "Action": "ssm:TerminateSession", + "Condition": { + "StringEquals": { + "aws:RequestedRegion": "eu-west-1", + }, + "StringLike": { + "ssm:resourceTag/aws:ssmmessages:session-id": "\${aws:userid}*", + }, + }, + "Effect": "Allow", + "Resource": "*", + }, + { + "Action": "ssm:ResumeSession", "Condition": { "StringEquals": { "aws:RequestedRegion": "eu-west-1", diff --git a/cdk/lib/__snapshots__/whole-stack.test.ts.snap b/cdk/lib/__snapshots__/whole-stack.test.ts.snap index ef87dfa1..152a519a 100644 --- a/cdk/lib/__snapshots__/whole-stack.test.ts.snap +++ b/cdk/lib/__snapshots__/whole-stack.test.ts.snap @@ -3952,10 +3952,20 @@ exports[`createStacks > should create WiresFeeds and Newswires stacks with corre ], }, { - "Action": [ - "ssm:TerminateSession", - "ssm:ResumeSession", - ], + "Action": "ssm:TerminateSession", + "Condition": { + "StringEquals": { + "aws:RequestedRegion": "eu-west-1", + }, + "StringLike": { + "ssm:resourceTag/aws:ssmmessages:session-id": "\${aws:userid}*", + }, + }, + "Effect": "Allow", + "Resource": "*", + }, + { + "Action": "ssm:ResumeSession", "Condition": { "StringEquals": { "aws:RequestedRegion": "eu-west-1", diff --git a/cdk/lib/local-run-developer-policy.ts b/cdk/lib/local-run-developer-policy.ts index 3b901849..2f1ee251 100644 --- a/cdk/lib/local-run-developer-policy.ts +++ b/cdk/lib/local-run-developer-policy.ts @@ -74,8 +74,8 @@ export function createLocalRunDeveloperPolicy( * 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. + * restrict requests to the stack's regional endpoint, since all of + * the relevant resources are in eu-west-1. */ new PolicyStatement({ effect: Effect.ALLOW, @@ -125,19 +125,32 @@ export function createLocalRunDeveloperPolicy( ], }), /** - * 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. + * Purpose: Let a developer close sessions they started. Session + * Manager applies `aws:ssmmessages:session-id` to each session; for + * an assumed role its value starts with `${aws:userid}`, so this works + * for Janus credentials without reconstructing the session ARN. */ new PolicyStatement({ effect: Effect.ALLOW, - actions: ['ssm:TerminateSession', 'ssm:ResumeSession'], + actions: ['ssm:TerminateSession'], + resources: ['*'], + conditions: { + StringEquals: { 'aws:RequestedRegion': region }, + StringLike: { + 'ssm:resourceTag/aws:ssmmessages:session-id': '${aws:userid}*', + }, + }, + }), + /** + * ResumeSession supports session resources, but an assumed role's + * `${aws:userid}` does not match the session ARN. AWS documents the + * system-tag ownership condition above for TerminateSession only, so + * resume remains available for sessions through the stack's regional + * endpoint. + */ + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['ssm:ResumeSession'], resources: ['*'], conditions: { StringEquals: { 'aws:RequestedRegion': region }, diff --git a/package-lock.json b/package-lock.json index 919c15de..7a694a46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3972,6 +3972,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -3989,6 +3990,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4006,6 +4008,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4023,6 +4026,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4040,6 +4044,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4057,6 +4062,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4074,6 +4080,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4091,6 +4098,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4108,6 +4116,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4125,6 +4134,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4142,6 +4152,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4159,6 +4170,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4176,6 +4188,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4193,6 +4206,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4210,6 +4224,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4227,6 +4242,7 @@ "os": [ "openharmony" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4241,6 +4257,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@emnapi/core": "1.9.2", "@emnapi/runtime": "1.9.2", @@ -4257,6 +4274,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@tybys/wasm-util": "^0.10.3" }, @@ -4282,6 +4300,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4299,6 +4318,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4316,6 +4336,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4342,7 +4363,8 @@ "optional": true, "os": [ "android" - ] + ], + "peer": true }, "node_modules/@oxc-resolver/binding-android-arm64": { "version": "11.21.3", @@ -4356,7 +4378,8 @@ "optional": true, "os": [ "android" - ] + ], + "peer": true }, "node_modules/@oxc-resolver/binding-darwin-arm64": { "version": "11.21.3", @@ -4370,7 +4393,8 @@ "optional": true, "os": [ "darwin" - ] + ], + "peer": true }, "node_modules/@oxc-resolver/binding-darwin-x64": { "version": "11.21.3", @@ -4384,7 +4408,8 @@ "optional": true, "os": [ "darwin" - ] + ], + "peer": true }, "node_modules/@oxc-resolver/binding-freebsd-x64": { "version": "11.21.3", @@ -4398,7 +4423,8 @@ "optional": true, "os": [ "freebsd" - ] + ], + "peer": true }, "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { "version": "11.21.3", @@ -4412,7 +4438,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { "version": "11.21.3", @@ -4426,7 +4453,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { "version": "11.21.3", @@ -4440,7 +4468,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@oxc-resolver/binding-linux-arm64-musl": { "version": "11.21.3", @@ -4454,7 +4483,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { "version": "11.21.3", @@ -4468,7 +4498,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { "version": "11.21.3", @@ -4482,7 +4513,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { "version": "11.21.3", @@ -4496,7 +4528,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { "version": "11.21.3", @@ -4510,7 +4543,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@oxc-resolver/binding-linux-x64-gnu": { "version": "11.21.3", @@ -4524,7 +4558,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@oxc-resolver/binding-linux-x64-musl": { "version": "11.21.3", @@ -4538,7 +4573,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@oxc-resolver/binding-openharmony-arm64": { "version": "11.21.3", @@ -4552,7 +4588,8 @@ "optional": true, "os": [ "openharmony" - ] + ], + "peer": true }, "node_modules/@oxc-resolver/binding-wasm32-wasi": { "version": "11.21.3", @@ -4564,6 +4601,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@emnapi/core": "1.11.0", "@emnapi/runtime": "1.11.0", @@ -4580,6 +4618,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" @@ -4592,6 +4631,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -4603,6 +4643,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -4614,6 +4655,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@tybys/wasm-util": "^0.10.3" }, @@ -4638,7 +4680,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@oxc-resolver/binding-win32-x64-msvc": { "version": "11.21.3", @@ -4652,7 +4695,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@peculiar/asn1-schema": { "version": "2.6.0",