From 2ee7f0774b2088b97e6c316be09915e18e72b751 Mon Sep 17 00:00:00 2001 From: Jamison French Date: Tue, 13 Jan 2026 17:19:32 -0600 Subject: [PATCH 1/2] feat: add patch manager IaC --- cdk/PatchManager.ts | 90 +++++++++++++++++++++++++++++++++++++++++++++ cdk/app.ts | 5 +++ 2 files changed, 95 insertions(+) create mode 100644 cdk/PatchManager.ts diff --git a/cdk/PatchManager.ts b/cdk/PatchManager.ts new file mode 100644 index 0000000..af76662 --- /dev/null +++ b/cdk/PatchManager.ts @@ -0,0 +1,90 @@ +import { Stack, StackProps } from 'aws-cdk-lib'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import { Construct } from 'constructs'; + +export class PatchManagerStack extends Stack { + constructor(scope: Construct, id: string, props?: Props) { + super(scope, id, props); + + // IAM role used by the maintenance window + const maintenanceRole = new iam.Role(this, 'MaintenanceWindowRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + }); + + maintenanceRole.addToPolicy( + new iam.PolicyStatement({ + actions: [ + 'ssm:SendCommand', + 'ssm:ListCommands', + 'ssm:ListCommandInvocations', + ], + resources: ['*'], + }), + ); + + // Maintenance Window + const maintenanceWindow = new ssm.CfnMaintenanceWindow( + this, + 'PatchMaintenanceWindow', + { + name: 'patch-maintenance-window', + description: 'Weekly patching using AWS default patch baseline', + schedule: 'cron(0 3 ? * SUN *)', // Sundays 03:00 UTC + duration: 3, + cutoff: 1, + allowUnassociatedTargets: false, + }, + ); + + // Target EC2 instances by Name tag + const target = new ssm.CfnMaintenanceWindowTarget( + this, + 'PatchTarget', + { + windowId: maintenanceWindow.ref, + resourceType: 'INSTANCE', + targets: [ + { + key: 'tag:Name', + values: [ + `MAAP-STAC-${props?.stage}-pgSTAC-pgbouncer`, + `MAAP-STAC-${props?.stage}-userSTAC-pgbouncer`, + ], + }, + ], + }, + ); + + // Patch task (Install) + new ssm.CfnMaintenanceWindowTask(this, 'PatchInstallTask', { + windowId: maintenanceWindow.ref, + taskArn: 'AWS-RunPatchBaseline', + taskType: 'RUN_COMMAND', + priority: 1, + maxConcurrency: '2', + maxErrors: '1', + serviceRoleArn: maintenanceRole.roleArn, + targets: [ + { + key: 'WindowTargetIds', + values: [target.ref], + }, + ], + taskInvocationParameters: { + maintenanceWindowRunCommandParameters: { + parameters: { + Operation: ['Install'], + }, + }, + }, + }); + } +} + +export interface Props extends StackProps { + /** + * Stage of this stack. Used for naming resources. + */ + stage: string; +} diff --git a/cdk/app.ts b/cdk/app.ts index c8faa0e..1e7972b 100644 --- a/cdk/app.ts +++ b/cdk/app.ts @@ -6,6 +6,7 @@ import { Vpc } from "./Vpc"; import { Config } from "./config"; import { PgStacInfra } from "./PgStacInfra"; import { MaapEoapiCommon } from "./MaapEoapiCommon"; +import { PatchManagerStack } from "./PatchManager"; const { buildStackName, @@ -127,3 +128,7 @@ new PgStacInfra(app, buildStackName("userSTAC"), { }), terminationProtection: false, }); + +new PatchManagerStack(app, buildStackName("patch-manager"), { + stage, +}); From 2bca53f090b3eb1b6bead825264e3bf7591862fb Mon Sep 17 00:00:00 2001 From: Jamison French Date: Wed, 14 Jan 2026 00:22:27 -0600 Subject: [PATCH 2/2] chore: modify cron --- cdk/PatchManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cdk/PatchManager.ts b/cdk/PatchManager.ts index af76662..99f1b58 100644 --- a/cdk/PatchManager.ts +++ b/cdk/PatchManager.ts @@ -30,7 +30,7 @@ export class PatchManagerStack extends Stack { { name: 'patch-maintenance-window', description: 'Weekly patching using AWS default patch baseline', - schedule: 'cron(0 3 ? * SUN *)', // Sundays 03:00 UTC + schedule: 'cron(0 7 ? * WED *)', // Wednesdays 07:00 UTC duration: 3, cutoff: 1, allowUnassociatedTargets: false,