Skip to content

Commit

Permalink
feat(HIPAA Security): IAM Checks (#360)
Browse files Browse the repository at this point in the history
Closes #193 
Closes #195
Closes #196 
Closes #198 
Closes #200

----

*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 24, 2021
1 parent 35e6f33 commit 4e6d7a6
Show file tree
Hide file tree
Showing 9 changed files with 657 additions and 10 deletions.
12 changes: 9 additions & 3 deletions RULES.md

Large diffs are not rendered by default.

95 changes: 88 additions & 7 deletions src/HIPAA-Security/hipaa-security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ import {
hipaaSecurityELBv2ACMCertificateRequired,
} from './rules/elb';
import { hipaaSecurityEMRKerberosEnabled } from './rules/emr';
import {
hipaaSecurityIAMNoInlinePolicy,
hipaaSecurityIAMPolicyNoStatementsWithAdminAccess,
hipaaSecurityIAMPolicyNoStatementsWithFullAccess,
hipaaSecurityIAMUserGroupMembership,
hipaaSecurityIAMUserNoPolicies,
} from './rules/iam';
import {
hipaaSecurityLambdaConcurrency,
hipaaSecurityLambdaDlq,
Expand Down Expand Up @@ -85,7 +92,7 @@ export class HIPAASecurityChecks extends NagPack {
this.checkElasticBeanstalk(node, ignores);
this.checkELB(node, ignores);
this.checkEMR(node, ignores);
// this.checkIAM(node, ignores);
this.checkIAM(node, ignores);
this.checkLambda(node, ignores);
this.checkOpenSearch(node, ignores);
// this.checkRDS(node, ignores);
Expand Down Expand Up @@ -659,12 +666,86 @@ export class HIPAASecurityChecks extends NagPack {
}
}

// /**
// * Check IAM Resources
// * @param node the IConstruct to evaluate
// * @param ignores list of ignores for the resource
// */
// private checkIAM(node: CfnResource, ignores: any): void {}
/**
* Check IAM Resources
* @param node the IConstruct to evaluate
* @param ignores list of ignores for the resource
*/
private checkIAM(node: CfnResource, ignores: any): void {
if (
!this.ignoreRule(ignores, 'HIPAA.Security-IAMNoInlinePolicy') &&
!hipaaSecurityIAMNoInlinePolicy(node)
) {
const ruleId = 'HIPAA.Security-IAMNoInlinePolicy';
const info =
'The IAM Group, User, or Role contains an inline policy - (Control IDs: 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.308(a)(3)(ii)(B), 164.308(a)(4)(i), 164.308(a)(4)(ii)(A), 164.308(a)(4)(ii)(B), 164.308(a)(4)(ii)(C), 164.312(a)(1)).';
const explanation =
'AWS recommends to use managed policies instead of inline policies. The managed policies allow reusability, versioning and rolling back, and delegating permissions management.';
Annotations.of(node).addError(
this.createMessage(ruleId, info, explanation)
);
}
if (
!this.ignoreRule(
ignores,
'HIPAA.Security-IAMPolicyNoStatementsWithAdminAccess'
) &&
!hipaaSecurityIAMPolicyNoStatementsWithAdminAccess(node)
) {
const ruleId = 'HIPAA.Security-IAMPolicyNoStatementsWithAdminAccess';
const info =
'The IAM policy grants admin access - (Control IDs: 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.308(a)(3)(ii)(B), 164.308(a)(4)(i), 164.308(a)(4)(ii)(A), 164.308(a)(4)(ii)(B), 164.308(a)(4)(ii)(C), 164.312(a)(1)).';
const explanation =
'AWS Identity and Access Management (IAM) can help you incorporate the principles of least privilege and separation of duties with access permissions and authorizations, restricting policies from containing "Effect": "Allow" with "Action": "*" over "Resource": "*". Allowing users to have more privileges than needed to complete a task may violate the principle of least privilege and separation of duties.';
Annotations.of(node).addError(
this.createMessage(ruleId, info, explanation)
);
}

if (
!this.ignoreRule(
ignores,
'HIPAA.Security-IAMPolicyNoStatementsWithFullAccess'
) &&
!hipaaSecurityIAMPolicyNoStatementsWithFullAccess(node)
) {
const ruleId = 'HIPAA.Security-IAMPolicyNoStatementsWithFullAccess';
const info =
'The IAM policy grants full access - (Control IDs: 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.308(a)(3)(ii)(B), 164.308(a)(4)(i), 164.308(a)(4)(ii)(A), 164.308(a)(4)(ii)(B), 164.308(a)(4)(ii)(C), 164.312(a)(1)).';
const explanation =
'Ensure IAM Actions are restricted to only those actions that are needed. Allowing users to have more privileges than needed to complete a task may violate the principle of least privilege and separation of duties.';
Annotations.of(node).addError(
this.createMessage(ruleId, info, explanation)
);
}
if (
!this.ignoreRule(ignores, 'HIPAA.Security-IAMUserGroupMembership') &&
!hipaaSecurityIAMUserGroupMembership(node)
) {
const ruleId = 'HIPAA.Security-IAMUserGroupMembership';
const info =
'The IAM user does not belong to any group(s) - (Control IDs: 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.308(a)(3)(ii)(B), 164.308(a)(4)(i), 164.308(a)(4)(ii)(A), 164.308(a)(4)(ii)(B), 164.308(a)(4)(ii)(C), 164.312(a)(1)).';
const explanation =
'AWS Identity and Access Management (IAM) can help you restrict access permissions and authorizations, by ensuring IAM users are members of at least one group. Allowing users more privileges than needed to complete a task may violate the principle of least privilege and separation of duties.';
Annotations.of(node).addError(
this.createMessage(ruleId, info, explanation)
);
}

if (
!this.ignoreRule(ignores, 'HIPAA.Security-IAMUserNoPolicies') &&
!hipaaSecurityIAMUserNoPolicies(node)
) {
const ruleId = 'HIPAA.Security-IAMUserNoPolicies';
const info =
'The IAM policy is attached at the user level - (Control IDs: 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.308(a)(3)(ii)(B), 164.308(a)(4)(i), 164.308(a)(4)(ii)(A), 164.308(a)(4)(ii)(B), 164.308(a)(4)(ii)(C), 164.312(a)(1)).';
const explanation =
'Assigning privileges at the group or the role level helps to reduce opportunity for an identity to receive or retain excessive privileges.';
Annotations.of(node).addError(
this.createMessage(ruleId, info, explanation)
);
}
}

/**
* Check Lambda Resources
Expand Down
27 changes: 27 additions & 0 deletions src/HIPAA-Security/rules/iam/hipaaSecurityIAMNoInlinePolicy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
import { CfnRole, CfnUser, CfnGroup, CfnPolicy } from '@aws-cdk/aws-iam';
import { IConstruct, Stack } from '@aws-cdk/core';

/**
* IAM Group, User, and Roles do not contain inline policies - (Control IDs: 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.308(a)(3)(ii)(B), 164.308(a)(4)(i), 164.308(a)(4)(ii)(A), 164.308(a)(4)(ii)(B), 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 CfnGroup ||
node instanceof CfnUser ||
node instanceof CfnRole
) {
const inlinePolicies = Stack.of(node).resolve(node.policies);
if (inlinePolicies != undefined) {
return false;
}
}
if (node instanceof CfnPolicy) {
return false;
}
return true;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
import {
CfnPolicy,
CfnManagedPolicy,
PolicyDocument,
CfnGroup,
CfnRole,
} from '@aws-cdk/aws-iam';
import { IConstruct, Stack } from '@aws-cdk/core';

/**
* IAM policies do not grant admin access - (Control IDs: 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.308(a)(3)(ii)(B), 164.308(a)(4)(i), 164.308(a)(4)(ii)(A), 164.308(a)(4)(ii)(B), 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 CfnPolicy || node instanceof CfnManagedPolicy) {
if (checkDocument(node, node.policyDocument)) {
return false;
}
} else if (node instanceof CfnGroup || node instanceof CfnRole) {
if (node.policies != undefined && checkDocument(node, node.policies)) {
return false;
}
}
return true;
}

/**
* Helper function for parsing through the policy document
* @param node the CfnResource to Check
* @param policyDoc the JSON policy document
* @returns boolean
*/
function checkDocument(node: IConstruct, policyDoc: any): boolean {
const resolvedDoc = Stack.of(node).resolve(policyDoc) as PolicyDocument;
const reg =
/"Action":\[?(.*,)?"\*"(,.*)?\]?,"Effect":"Allow","Resource":\[?(.*,)?"(?:arn(?::.*(?::)?)?)?\*"(,.*)?\]?/gm;
if (JSON.stringify(resolvedDoc).search(reg) != -1) {
return true;
}
return false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
import {
CfnPolicy,
CfnManagedPolicy,
PolicyDocument,
CfnGroup,
CfnRole,
} from '@aws-cdk/aws-iam';
import { IConstruct, Stack } from '@aws-cdk/core';

/**
* IAM policies do not grant full access - (Control IDs: 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.308(a)(3)(ii)(B), 164.308(a)(4)(i), 164.308(a)(4)(ii)(A), 164.308(a)(4)(ii)(B), 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 CfnPolicy || node instanceof CfnManagedPolicy) {
if (checkDocument(node, node.policyDocument)) {
return false;
}
} else if (node instanceof CfnGroup || node instanceof CfnRole) {
if (node.policies != undefined && checkDocument(node, node.policies)) {
return false;
}
}
return true;
}

/**
* Helper function for parsing through the policy document
* @param node the CfnResource to Check
* @param policyDoc the JSON policy document
* @returns boolean
*/
function checkDocument(node: IConstruct, policyDoc: any): boolean {
const resolvedDoc = Stack.of(node).resolve(policyDoc) as PolicyDocument;
const reg = /"Action":\[?(.*,)?"(?:\w+:)?\*"(,.*)?\]?,"Effect":"Allow"/gm;
if (JSON.stringify(resolvedDoc).search(reg) != -1) {
return true;
}
return false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
import { CfnUser } from '@aws-cdk/aws-iam';
import { IConstruct, Stack } from '@aws-cdk/core';

/**
* IAM users are assigned to at least one group - (Control IDs: 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.308(a)(3)(ii)(B), 164.308(a)(4)(i), 164.308(a)(4)(ii)(A), 164.308(a)(4)(ii)(B), 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 CfnUser) {
const userGroup = Stack.of(node).resolve(node.groups);
if (userGroup == undefined) {
return false;
}
}
return true;
}
25 changes: 25 additions & 0 deletions src/HIPAA-Security/rules/iam/hipaaSecurityIAMUserNoPolicies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
import { CfnPolicy, CfnManagedPolicy, CfnUser } from '@aws-cdk/aws-iam';
import { IConstruct, Stack } from '@aws-cdk/core';
/**
* IAM policies are not attached at the user level - (Control IDs: 164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.308(a)(3)(ii)(B), 164.308(a)(4)(i), 164.308(a)(4)(ii)(A), 164.308(a)(4)(ii)(B), 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 CfnPolicy || node instanceof CfnManagedPolicy) {
const policyUsers = Stack.of(node).resolve(node.users);
if (policyUsers != undefined) {
return false;
}
} else if (node instanceof CfnUser) {
const policies = Stack.of(node).resolve(node.policies);
const managedPolicyArns = Stack.of(node).resolve(node.managedPolicyArns);
if (policies != undefined || managedPolicyArns != undefined) {
return false;
}
}
return true;
}
5 changes: 5 additions & 0 deletions src/HIPAA-Security/rules/iam/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
export { default as hipaaSecurityIAMNoInlinePolicy } from './hipaaSecurityIAMNoInlinePolicy';
export { default as hipaaSecurityIAMPolicyNoStatementsWithAdminAccess } from './hipaaSecurityIAMPolicyNoStatementsWithAdminAccess';
export { default as hipaaSecurityIAMPolicyNoStatementsWithFullAccess } from './hipaaSecurityIAMPolicyNoStatementsWithFullAccess';
export { default as hipaaSecurityIAMUserGroupMembership } from './hipaaSecurityIAMUserGroupMembership';
export { default as hipaaSecurityIAMUserNoPolicies } from './hipaaSecurityIAMUserNoPolicies';
Loading

0 comments on commit 4e6d7a6

Please sign in to comment.