Skip to content

Commit

Permalink
add patch manager component (#380)
Browse files Browse the repository at this point in the history
* add patch manager component

* format and set schedule

* fix eslint problems
  • Loading branch information
snowiow authored Jan 18, 2024
1 parent ae12400 commit 76e71e9
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 11 deletions.
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
},
"rules": {
"@typescript-eslint/consistent-type-definitions": ["error", "interface"],
"@typescript-eslint/consistent-type-assertions": [2, { "assertionStyle": "as" }]
"@typescript-eslint/consistent-type-assertions": [2, { "assertionStyle": "as" }],
"no-new": 0
}
}
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ Currently the following AWS Services are supported:
| Redshift | `BastionHostRedshiftForward` |
| RDS | `BastionHostRDSForward` |

# V3 DISCLAIMER

With version 3 a patch manager component is included so that the bastion host instance is provided with security updates on a regular basis. These happen in a maintenance window every sunday at 3am (timezone where it's deployed). To disable the patching, you need to provide the attribute `shouldPatch: false`.

Example:

```typescript
new GenericBastionHostForward(this, 'BastionHostRedshiftForward', {
vpc,
securityGroup,
address,
port,
shouldPatch: false,
});
```

# V1 DISCLAIMER

We introduced v1.0.0 recently, which now relies on v2 of CDK. This introced an
Expand Down
3 changes: 2 additions & 1 deletion lib/aurora-serverless.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2020 MOIA GmbH
Copyright 2024 MOIA GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Expand Down Expand Up @@ -53,6 +53,7 @@ export class BastionHostAuroraServerlessForward extends BastionHostForward {
port: Token.asString(props.serverlessCluster.clusterEndpoint.port),
clientTimeout: props.clientTimeout,
serverTimeout: props.serverTimeout,
shouldPatch: props.shouldPatch,
});

if (props.iamUser !== undefined && props.resourceIdentifier !== undefined) {
Expand Down
9 changes: 8 additions & 1 deletion lib/bastion-host-forward-base-props.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2020 MOIA GmbH
Copyright 2024 MOIA GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Expand Down Expand Up @@ -47,4 +47,11 @@ export interface BastionHostForwardBaseProps {
* @default 1
*/
readonly serverTimeout?: number;

/**
* Whether patching should be enabled for the bastion-host-forward instance
*
* @default true
*/
readonly shouldPatch?: boolean;
}
17 changes: 15 additions & 2 deletions lib/bastion-host-forward.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2020 MOIA GmbH
Copyright 2024 MOIA GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Expand All @@ -25,9 +25,11 @@ import {
UserData,
} from 'aws-cdk-lib/aws-ec2';
import type { CfnInstance, ISecurityGroup } from 'aws-cdk-lib/aws-ec2';
import { ManagedPolicy } from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

import type { BastionHostForwardProps } from './bastion-host-forward-props';
import { BastionHostPatchManager } from './bastion-host-patch-manager';

interface HaProxyConfig {
address: string;
Expand Down Expand Up @@ -111,9 +113,10 @@ export class BastionHostForward extends Construct {
allowAllOutbound: true,
});

const instanceName = props.name ?? 'BastionHost';
this.bastionHost = new BastionHostLinux(this, 'BastionHost', {
requireImdsv2: true,
instanceName: props.name ?? 'BastionHost',
instanceName,
machineImage: new AmazonLinuxImage({
cpuType: AmazonLinuxCpuType.ARM_64,
generation: AmazonLinuxGeneration.AMAZON_LINUX_2023,
Expand All @@ -140,6 +143,16 @@ export class BastionHostForward extends Construct {
});
cfnBastionHost.userData = Fn.base64(shellCommands.render());

if (props.shouldPatch === undefined || props.shouldPatch) {
this.bastionHost.instance.role.addManagedPolicy(
ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'),
);
new BastionHostPatchManager(this, 'BastionHostPatchManager', {
instanceName,
instanceId: this.bastionHost.instance.instanceId,
});
}

this.instanceId = this.bastionHost.instance.instanceId;
this.instancePrivateIp = this.bastionHost.instance.instancePrivateIp;
}
Expand Down
75 changes: 75 additions & 0 deletions lib/bastion-host-patch-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
Copyright 2024 MOIA GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { CfnMaintenanceWindow, CfnMaintenanceWindowTarget, CfnMaintenanceWindowTask } from 'aws-cdk-lib/aws-ssm';
import { Construct } from 'constructs';

export interface BastionHostPatchManagerProps {
/**
* The instance id that should be patched with security updates
*/
readonly instanceId: string;
/**
* The name of the bastion host instance
*/
readonly instanceName: string;
}

export class BastionHostPatchManager extends Construct {
public constructor(scope: Construct, id: string, props: BastionHostPatchManagerProps) {
super(scope, id);
const maintenanceWindow = new CfnMaintenanceWindow(this, 'MaintenanceWindow', {
name: `Patch-${props.instanceName}`,
allowUnassociatedTargets: false,
cutoff: 0,
duration: 2,
schedule: 'cron(0 3 * * SUN *)',
});

const maintenanceTarget = new CfnMaintenanceWindowTarget(this, 'MaintenanceWindowTarget', {
name: `${props.instanceName}`,
windowId: maintenanceWindow.ref,
ownerInformation: 'Bastion-Host-Forward',
resourceType: 'INSTANCE',
targets: [
{
key: 'InstanceIds',
values: [props.instanceId],
},
],
});
new CfnMaintenanceWindowTask(this, 'MaintenanceWindowTask', {
taskArn: 'AWS-RunPatchBaseline',
priority: 1,
taskType: 'RUN_COMMAND',
windowId: maintenanceWindow.ref,
name: `${props.instanceName}-Patch-Task`,
targets: [
{
key: 'WindowTargetIds',
values: [maintenanceTarget.ref],
},
],
taskInvocationParameters: {
maintenanceWindowRunCommandParameters: {
parameters: {
Operation: ['Install'],
},
documentVersion: '$LATEST',
},
},
maxErrors: '0',
maxConcurrency: '1',
});
}
}
3 changes: 2 additions & 1 deletion lib/generic-bastion-host-forward.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2020 MOIA GmbH
Copyright 2024 MOIA GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Expand Down Expand Up @@ -32,6 +32,7 @@ export class GenericBastionHostForward extends BastionHostForward {
port: String(props.port),
clientTimeout: props.clientTimeout,
serverTimeout: props.serverTimeout,
shouldPatch: props.shouldPatch,
});
}
}
2 changes: 1 addition & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2020 MOIA GmbH
Copyright 2024 MOIA GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Expand Down
3 changes: 2 additions & 1 deletion lib/rds.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2020 MOIA GmbH
Copyright 2024 MOIA GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Expand Down Expand Up @@ -54,6 +54,7 @@ export class BastionHostRDSForward extends BastionHostForward {
port: props.rdsInstance.dbInstanceEndpointPort,
clientTimeout: props.clientTimeout,
serverTimeout: props.serverTimeout,
shouldPatch: props.shouldPatch,
});

if (props.iamUser !== undefined && props.rdsResourceIdentifier !== undefined) {
Expand Down
2 changes: 1 addition & 1 deletion test/aurora-serverless.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2020 MOIA GmbH
Copyright 2024 MOIA GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Expand Down
53 changes: 52 additions & 1 deletion test/generic-bastion-host-forward.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2020 MOIA GmbH
Copyright 2024 MOIA GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Expand Down Expand Up @@ -46,6 +46,37 @@ test('Bastion Host created for normal access', () => {
},
],
});

template.hasResource('AWS::SSM::MaintenanceWindow', {});
template.hasResourceProperties('AWS::SSM::MaintenanceWindowTarget', {
Targets: [
{
Key: 'InstanceIds',
Values: [
{
Ref: 'MyTestConstructBastionHost55102049',
},
],
},
],
WindowId: {
Ref: 'MyTestConstructBastionHostPatchManagerMaintenanceWindow4F21EBB0',
},
});

template.hasResourceProperties('AWS::SSM::MaintenanceWindowTask', {
Targets: [
{
Key: 'WindowTargetIds',
Values: [
{
Ref: 'MyTestConstructBastionHostPatchManagerMaintenanceWindowTarget1C708788',
},
],
},
],
TaskArn: 'AWS-RunPatchBaseline',
});
});

test('Bastion Host with own securityGroup', () => {
Expand Down Expand Up @@ -99,3 +130,23 @@ test('Bastion Host has encrypted EBS', () => {
],
});
});

test('Bastion Host created without patch manager', () => {
const app = new App();
const stack = new Stack(app, 'TestStack');
const testVpc = new Vpc(stack, 'TestVpc');
// WHEN
new GenericBastionHostForward(stack, 'MyTestConstruct', {
vpc: testVpc,
address: '127.0.0.1',
port: '6379',
shouldPatch: false,
});

const template = Template.fromStack(stack);

// THEN
template.resourceCountIs('AWS::SSM::MaintenanceWindow', 0);
template.resourceCountIs('AWS::SSM::MaintenanceWindowTarget', 0);
template.resourceCountIs('AWS::SSM::MaintenanceWindowTask', 0);
});
2 changes: 1 addition & 1 deletion test/rds.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2020 MOIA GmbH
Copyright 2024 MOIA GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Expand Down

0 comments on commit 76e71e9

Please sign in to comment.