Skip to content

Commit 18fc114

Browse files
authored
feat: add patching infrastructure
2 parents 5f73356 + 65f1e9e commit 18fc114

3 files changed

Lines changed: 103 additions & 2 deletions

File tree

cdk/PatchManager.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { Stack, StackProps } from 'aws-cdk-lib';
2+
import * as ssm from 'aws-cdk-lib/aws-ssm';
3+
import * as iam from 'aws-cdk-lib/aws-iam';
4+
import { Construct } from 'constructs';
5+
6+
export class PatchManagerStack extends Stack {
7+
constructor(scope: Construct, id: string, props: Props) {
8+
super(scope, id, props);
9+
10+
// IAM role used by the maintenance window
11+
const maintenanceRole = new iam.Role(this, 'MaintenanceWindowRole', {
12+
assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'),
13+
});
14+
15+
maintenanceRole.addToPolicy(
16+
new iam.PolicyStatement({
17+
actions: [
18+
'ssm:SendCommand',
19+
'ssm:ListCommands',
20+
'ssm:ListCommandInvocations',
21+
],
22+
resources: ['*'],
23+
}),
24+
);
25+
26+
// Maintenance Window
27+
const maintenanceWindow = new ssm.CfnMaintenanceWindow(
28+
this,
29+
'PatchMaintenanceWindow',
30+
{
31+
name: 'patch-maintenance-window',
32+
description: 'Weekly patching using AWS default patch baseline',
33+
schedule: 'cron(0 7 ? * WED *)', // Wednesdays 07:00 UTC
34+
duration: 3,
35+
cutoff: 1,
36+
allowUnassociatedTargets: false,
37+
},
38+
);
39+
40+
// Target EC2 instances by Name tag
41+
const target = new ssm.CfnMaintenanceWindowTarget(
42+
this,
43+
'PatchTarget',
44+
{
45+
windowId: maintenanceWindow.ref,
46+
resourceType: 'INSTANCE',
47+
targets: [
48+
{
49+
key: 'InstanceIds',
50+
values: [...props.instanceIds],
51+
},
52+
],
53+
},
54+
);
55+
56+
// Patch task (Install)
57+
new ssm.CfnMaintenanceWindowTask(this, 'PatchInstallTask', {
58+
windowId: maintenanceWindow.ref,
59+
taskArn: 'AWS-RunPatchBaseline',
60+
taskType: 'RUN_COMMAND',
61+
priority: 1,
62+
maxConcurrency: '2',
63+
maxErrors: '1',
64+
serviceRoleArn: maintenanceRole.roleArn,
65+
targets: [
66+
{
67+
key: 'WindowTargetIds',
68+
values: [target.ref],
69+
},
70+
],
71+
taskInvocationParameters: {
72+
maintenanceWindowRunCommandParameters: {
73+
parameters: {
74+
Operation: ['Install'],
75+
},
76+
},
77+
},
78+
});
79+
}
80+
}
81+
82+
export interface Props extends StackProps {
83+
/**
84+
* Instance IDs to target for patching.
85+
*/
86+
instanceIds: string[];
87+
}

cdk/PgStacInfra.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { load } from "js-yaml";
2727
import { DpsStacItemGenerator } from "./constructs/DpsStacItemGenerator";
2828

2929
export class PgStacInfra extends Stack {
30+
public readonly pgbouncerInstanceId: string;
3031
constructor(scope: Construct, id: string, props: Props) {
3132
super(scope, id, props);
3233

@@ -64,10 +65,14 @@ export class PgStacInfra extends Stack {
6465
allocatedStorage: pgstacDbConfig.allocatedStorage,
6566
instanceType: pgstacDbConfig.instanceType,
6667
addPgbouncer: true,
68+
addPatchManager: false,
6769
pgstacVersion: pgstacDbConfig.pgstacVersion,
6870
customResourceProperties: { context: true },
6971
bootstrapperLambdaFunctionOptions: { timeout: Duration.minutes(15) },
7072
});
73+
if (pgstacDb.pgbouncerInstanceId) {
74+
this.pgbouncerInstanceId = pgstacDb.pgbouncerInstanceId;
75+
}
7176

7277
const apiSubnetSelection: ec2.SubnetSelection = {
7378
subnetType: pgstacDbConfig.subnetPublic

cdk/app.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Vpc } from "./Vpc";
66
import { Config } from "./config";
77
import { PgStacInfra } from "./PgStacInfra";
88
import { MaapEoapiCommon } from "./MaapEoapiCommon";
9+
import { PatchManagerStack } from "./PatchManager";
910

1011
const {
1112
buildStackName,
@@ -49,7 +50,7 @@ const common = new MaapEoapiCommon(app, buildStackName("common"), {
4950
terminationProtection: false,
5051
});
5152

52-
new PgStacInfra(app, buildStackName("pgSTAC"), {
53+
const coreInfrastructure = new PgStacInfra(app, buildStackName("pgSTAC"), {
5354
vpc,
5455
tags,
5556
stage,
@@ -89,7 +90,7 @@ new PgStacInfra(app, buildStackName("pgSTAC"), {
8990
terminationProtection: false,
9091
});
9192

92-
new PgStacInfra(app, buildStackName("userSTAC"), {
93+
const userInfrastructure = new PgStacInfra(app, buildStackName("userSTAC"), {
9394
vpc,
9495
tags,
9596
stage,
@@ -127,3 +128,11 @@ new PgStacInfra(app, buildStackName("userSTAC"), {
127128
}),
128129
terminationProtection: false,
129130
});
131+
132+
new PatchManagerStack(app, buildStackName("patch-manager"), {
133+
instanceIds: [
134+
coreInfrastructure.pgbouncerInstanceId,
135+
userInfrastructure.pgbouncerInstanceId,
136+
],
137+
terminationProtection: false,
138+
});

0 commit comments

Comments
 (0)