Skip to content

Commit

Permalink
feat(HIPAA Security): ECS check (#344)
Browse files Browse the repository at this point in the history
Closes #243 

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
dontirun authored Sep 14, 2021
1 parent 49c55a0 commit aa40fed
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 7 deletions.
1 change: 1 addition & 0 deletions RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ There are currently no warnings for this rule pack.
| [HIPAA.Security-EC2InstanceDetailedMonitoringEnabled](https://docs.aws.amazon.com/config/latest/developerguide/ec2-instance-detailed-monitoring-enabled.html) | The EC2 instance does not have detailed monitoring enabled. | Enable this rule to help improve Amazon Elastic Compute Cloud (Amazon EC2) instance monitoring on the Amazon EC2 console, which displays monitoring graphs with a 1-minute period for the instance. | 164.312(b) |
| [HIPAA.Security-EC2InstanceNoPublicIp](https://docs.aws.amazon.com/config/latest/developerguide/ec2-instance-no-public-ip.html) | The EC2 instance is associated with a public IP address. | Manage access to the AWS Cloud by ensuring Amazon Elastic Compute Cloud (Amazon EC2) instances cannot be publicly accessed. Amazon EC2 instances can contain sensitive information and access control is required for such accounts. | 164.308(a)(3)(i), 164.308(a)(4)(ii)(A), 164.308(a)(4)(ii)(C), 164.312(a)(1), 164.312(e)(1) |
| [HIPAA.Security-EC2InstancesInVPC](https://docs.aws.amazon.com/config/latest/developerguide/ec2-instances-in-vpc.html) | The EC2 instance is not within a VPC. | Deploy Amazon Elastic Compute Cloud (Amazon EC2) instances within an Amazon Virtual Private Cloud (Amazon VPC) to enable secure communication between an instance and other services within the amazon VPC, without requiring an internet gateway, NAT device, or VPN connection. All traffic remains securely within the AWS Cloud. Because of their logical isolation, domains that reside within anAmazon VPC have an extra layer of security when compared to domains that use public endpoints. Assign Amazon EC2 instances to an Amazon VPC to properly manage access. | 164.308(a)(3)(i), 164.308(a)(4)(ii)(A), 164.308(a)(4)(ii)(C), 164.312(a)(1), 164.312(e)(1) |
| [HIPAA.Security-ECSTaskDefinitionUserForHostMode](https://docs.aws.amazon.com/config/latest/developerguide/ecs-task-definition-user-for-host-mode-check.html) | The ECS task definition is configured for host networking and has at least one container with definitions with 'privileged' set to false or empty or 'user' set to root or empty. | If a task definition has elevated privileges it is because you have specifically opted-in to those configurations. This rule checks for unexpected privilege escalation when a task definition has host networking enabled but the customer has not opted-in to elevated privileges. | 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.308(a)(4)(ii)(A), 164.308(a)(4)(ii)(C), 164.312(a)(1) |

### Excluded Rules

Expand Down
32 changes: 25 additions & 7 deletions src/HIPAA-Security/hipaa-security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
hipaaSecurityEC2InstancesInVPC,
hipaaSecurityEC2InstanceNoPublicIp,
} from './rules/ec2';
import { hipaaSecurityECSTaskDefinitionUserForHostMode } from './rules/ecs';

/**
* Check for HIPAA Security compliance.
Expand All @@ -54,7 +55,7 @@ export class HIPAASecurityChecks extends NagPack {
this.checkDMS(node, ignores);
this.checkDynamoDB(node, ignores);
this.checkEC2(node, ignores);
// this.checkECS(node, ignores);
this.checkECS(node, ignores);
// this.checkEFS(node, ignores);
// this.checkElastiCache(node, ignores);
// this.checkElasticBeanstalk(node, ignores);
Expand Down Expand Up @@ -398,12 +399,29 @@ export class HIPAASecurityChecks extends NagPack {
}
}

// /**
// * Check ECS Resources
// * @param node the IConstruct to evaluate
// * @param ignores list of ignores for the resource
// */
// private checkECS(node: CfnResource, ignores: any): void {}
/**
* Check ECS Resources
* @param node the IConstruct to evaluate
* @param ignores list of ignores for the resource
*/
private checkECS(node: CfnResource, ignores: any): void {
if (
!this.ignoreRule(
ignores,
'HIPAA.Security-ECSTaskDefinitionUserForHostMode'
) &&
!hipaaSecurityECSTaskDefinitionUserForHostMode(node)
) {
const ruleId = 'HIPAA.Security-ECSTaskDefinitionUserForHostMode';
const info =
"The ECS task definition is configured for host networking and has at least one container with definitions with 'privileged' set to false or empty or 'user' set to root or empty - (Control IDs: 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.308(a)(4)(ii)(A), 164.308(a)(4)(ii)(C), 164.312(a)(1)).";
const explanation =
'If a task definition has elevated privileges it is because you have specifically opted-in to those configurations. This rule checks for unexpected privilege escalation when a task definition has host networking enabled but the customer has not opted-in to elevated privileges.';
Annotations.of(node).addError(
this.createMessage(ruleId, info, explanation)
);
}
}

// /**
// * Check EFS Resources
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
import { CfnTaskDefinition, NetworkMode } from '@aws-cdk/aws-ecs';
import { IConstruct, Stack } from '@aws-cdk/core';

/**
* Containers in ECS task definitions configured for host networking have 'privileged' set to true and a non-empty non-root 'user' - (Control IDs: 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.308(a)(4)(ii)(A), 164.308(a)(4)(ii)(C), 164.312(a)(1))
* @param node the CfnResource to check
*/
export default function (node: IConstruct): boolean {
if (node instanceof CfnTaskDefinition) {
if (node.networkMode === NetworkMode.HOST) {
const containerDefinitions = Stack.of(node).resolve(
node.containerDefinitions
);
if (containerDefinitions !== undefined) {
for (const containerDefinition of containerDefinitions) {
const resolvedDefinition =
Stack.of(node).resolve(containerDefinition);
const privileged = Stack.of(node).resolve(
resolvedDefinition.privileged
);
const user = Stack.of(node).resolve(resolvedDefinition.user);
if (privileged !== true || user === undefined) {
return false;
}
const rootIdentifiers = ['root', '0'];
const userParts = user.split(':');
for (const userPart of userParts) {
if (rootIdentifiers.includes(userPart.toLowerCase())) {
return false;
}
}
}
}
}
}
return true;
}
1 change: 1 addition & 0 deletions src/HIPAA-Security/rules/ecs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
export { default as hipaaSecurityECSTaskDefinitionUserForHostMode } from './hipaaSecurityECSTaskDefinitionUserForHostMode';
163 changes: 163 additions & 0 deletions test/HIPAA-Security/HIPAA-Security-ECS.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

import { SynthUtils } from '@aws-cdk/assert';
import {
TaskDefinition,
Compatibility,
NetworkMode,
EcsOptimizedImage,
} from '@aws-cdk/aws-ecs';
import { Aspects, Stack } from '@aws-cdk/core';
import { HIPAASecurityChecks } from '../../src';

describe('Amazon Elastic Container Service (Amazon ECS', () => {
test("hipaaSecurityECSTaskDefinitionUserForHostMode: - Containers in ECS task definitions configured for host networking have 'privileged' set to true and a non-empty non-root 'user' - (Control IDs: 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.308(a)(4)(ii)(A), 164.308(a)(4)(ii)(C), 164.312(a)(1)).", () => {
const nonCompliant = new Stack();
Aspects.of(nonCompliant).add(new HIPAASecurityChecks());
new TaskDefinition(nonCompliant, 'rTaskDef', {
compatibility: Compatibility.EC2,
networkMode: NetworkMode.HOST,
}).addContainer('rContainer', {
image: EcsOptimizedImage,
memoryReservationMiB: 42,
});
const messages = SynthUtils.synthesize(nonCompliant).messages;
expect(messages).toContainEqual(
expect.objectContaining({
entry: expect.objectContaining({
data: expect.stringContaining(
'HIPAA.Security-ECSTaskDefinitionUserForHostMode:'
),
}),
})
);

const nonCompliant2 = new Stack();
Aspects.of(nonCompliant2).add(new HIPAASecurityChecks());
new TaskDefinition(nonCompliant2, 'rTaskDef', {
compatibility: Compatibility.EC2,
networkMode: NetworkMode.HOST,
}).addContainer('rContainer', {
image: EcsOptimizedImage,
memoryReservationMiB: 42,
user: 'ec2-user',
});
const messages2 = SynthUtils.synthesize(nonCompliant).messages;
expect(messages2).toContainEqual(
expect.objectContaining({
entry: expect.objectContaining({
data: expect.stringContaining(
'HIPAA.Security-ECSTaskDefinitionUserForHostMode:'
),
}),
})
);

const nonCompliant3 = new Stack();
Aspects.of(nonCompliant3).add(new HIPAASecurityChecks());
new TaskDefinition(nonCompliant3, 'rTaskDef', {
compatibility: Compatibility.EC2,
networkMode: NetworkMode.HOST,
}).addContainer('rContainer', {
image: EcsOptimizedImage,
memoryReservationMiB: 42,
privileged: true,
user: '0',
});
const messages3 = SynthUtils.synthesize(nonCompliant).messages;
expect(messages3).toContainEqual(
expect.objectContaining({
entry: expect.objectContaining({
data: expect.stringContaining(
'HIPAA.Security-ECSTaskDefinitionUserForHostMode:'
),
}),
})
);

const nonCompliant4 = new Stack();
Aspects.of(nonCompliant4).add(new HIPAASecurityChecks());
new TaskDefinition(nonCompliant4, 'rTaskDef', {
compatibility: Compatibility.EC2,
networkMode: NetworkMode.HOST,
}).addContainer('rContainer', {
image: EcsOptimizedImage,
memoryReservationMiB: 42,
privileged: true,
user: '0:root',
});
const messages4 = SynthUtils.synthesize(nonCompliant).messages;
expect(messages4).toContainEqual(
expect.objectContaining({
entry: expect.objectContaining({
data: expect.stringContaining(
'HIPAA.Security-ECSTaskDefinitionUserForHostMode:'
),
}),
})
);

const nonCompliant5 = new Stack();
Aspects.of(nonCompliant5).add(new HIPAASecurityChecks());
const taskDef = new TaskDefinition(nonCompliant5, 'rTaskDef', {
compatibility: Compatibility.EC2,
networkMode: NetworkMode.HOST,
});
taskDef.addContainer('rContainer', {
image: EcsOptimizedImage,
memoryReservationMiB: 42,
privileged: true,
user: 'not-root',
});
taskDef.addContainer('rContainer2', {
image: EcsOptimizedImage,
memoryReservationMiB: 42,
});
const messages5 = SynthUtils.synthesize(nonCompliant).messages;
expect(messages5).toContainEqual(
expect.objectContaining({
entry: expect.objectContaining({
data: expect.stringContaining(
'HIPAA.Security-ECSTaskDefinitionUserForHostMode:'
),
}),
})
);

const compliant = new Stack();
Aspects.of(compliant).add(new HIPAASecurityChecks());
new TaskDefinition(compliant, 'rTaskDef', {
compatibility: Compatibility.EC2,
networkMode: NetworkMode.HOST,
});
new TaskDefinition(compliant, 'rTaskDef2', {
compatibility: Compatibility.EC2,
networkMode: NetworkMode.HOST,
}).addContainer('rContainer2', {
image: EcsOptimizedImage,
memoryReservationMiB: 42,
privileged: true,
user: 'not-root',
});
new TaskDefinition(compliant, 'rTaskDef3', {
compatibility: Compatibility.EC2,
networkMode: NetworkMode.BRIDGE,
}).addContainer('rContainer3', {
image: EcsOptimizedImage,
memoryReservationMiB: 42,
});
const messages6 = SynthUtils.synthesize(compliant).messages;
expect(messages6).not.toContainEqual(
expect.objectContaining({
entry: expect.objectContaining({
data: expect.stringContaining(
'HIPAA.Security-ECSTaskDefinitionUserForHostMode:'
),
}),
})
);
});
});

0 comments on commit aa40fed

Please sign in to comment.