Skip to content

Commit

Permalink
Merge pull request #97 from vibe-io/task/aws-config-security
Browse files Browse the repository at this point in the history
feat: Task/aws config security
  • Loading branch information
Pharrox committed Aug 9, 2023
2 parents 1845719 + 7ffa1e6 commit 5b16174
Show file tree
Hide file tree
Showing 22 changed files with 7,203 additions and 0 deletions.
6,010 changes: 6,010 additions & 0 deletions API.md

Large diffs are not rendered by default.

151 changes: 151 additions & 0 deletions src/config-rules/iam-password-policy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { ResourceProps } from 'aws-cdk-lib';
import { ManagedRule, MaximumExecutionFrequency } from 'aws-cdk-lib/aws-config';
import { Effect, ManagedPolicy, PolicyStatement, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
import { IConstruct } from 'constructs';
import { RemediationTarget } from '../config/lib';
import { RemediationConfiguration } from '../config/remediation-configuration';
import { AutomationDocument } from '../ssm';


export interface IamPasswordPolicyProps extends ResourceProps {
readonly autoRemediation?: boolean;
readonly configRuleName?: string;
readonly description?: string;
readonly maxPasswordAge?: number;
readonly maximumExecutionFrequency?: MaximumExecutionFrequency;
readonly minimumPasswordLength?: number;
readonly passwordReusePrevention?: number;
readonly requireLowercaseCharacters?: boolean;
readonly requireNumbers?: boolean;
readonly requireSymbols?: boolean;
readonly requireUppercaseCharacters?: boolean;
}

export class IamPasswordPolicy extends ManagedRule {
static readonly DEFAULT_DESCRIPTION: string = [
'Checks if the account password policy for AWS Identity and Access',
'Management (IAM) users meets the specified requirements indicated in the',
'parameters. The rule is NON_COMPLIANT if the account password policy',
'does not meet the specified requirements.',
].join(' ');
static readonly DEFAULT_MAX_PASSWORD_AGE: number = 90;
static readonly DEFAULT_MINIMUM_PASSWORD_LENGTH: number = 14;
static readonly DEFAULT_PASSWORD_REUSE_PREVENTION: number = 24;
static readonly DEFAULT_REQUIRE_LOWERCASE_CHARACTERS: boolean = true;
static readonly DEFAULT_REQUIRE_NUMBERS: boolean = true;
static readonly DEFAULT_REQUIRE_SYMBOLS: boolean = true;
static readonly DEFAULT_REQUIRE_UPPERCASE_CHARACTERS: boolean = true;
static readonly MANAGED_RULE_NAME: string = 'IAM_PASSWORD_POLICY';
static readonly REMEDIATION_DOCUMENT_NAME: string = 'AWSConfigRemediation-SetIAMPasswordPolicy';


readonly maxPasswordAge: number;
readonly minimumPasswordLength: number;
readonly passwordReusePrevention: number;
readonly requireLowercaseCharacters: boolean;
readonly requireNumbers: boolean;
readonly requireSymbols: boolean;
readonly requireUppercaseCharacters: boolean;
readonly remediationConfiguration: RemediationConfiguration;
readonly remediationPolicy: ManagedPolicy;
readonly remediationRole: Role;


public constructor(scope: IConstruct, id: string, props: IamPasswordPolicyProps) {
const managedRuleName = IamPasswordPolicy.MANAGED_RULE_NAME;
const maxPasswordAge = props.maxPasswordAge ?? IamPasswordPolicy.DEFAULT_MAX_PASSWORD_AGE;
const minimumPasswordLength = props.minimumPasswordLength ?? IamPasswordPolicy.DEFAULT_MINIMUM_PASSWORD_LENGTH;
const passwordReusePrevention = props.passwordReusePrevention ?? IamPasswordPolicy.DEFAULT_PASSWORD_REUSE_PREVENTION;
const requireLowercaseCharacters = props.requireLowercaseCharacters ?? IamPasswordPolicy.DEFAULT_REQUIRE_UPPERCASE_CHARACTERS;
const requireNumbers = props.requireNumbers ?? IamPasswordPolicy.DEFAULT_REQUIRE_NUMBERS;
const requireSymbols = props.requireSymbols ?? IamPasswordPolicy.DEFAULT_REQUIRE_SYMBOLS;
const requireUppercaseCharacters = props.requireUppercaseCharacters ?? IamPasswordPolicy.DEFAULT_REQUIRE_UPPERCASE_CHARACTERS;

super(scope, id, {
configRuleName: props.configRuleName,
description: props.description ?? IamPasswordPolicy.DEFAULT_DESCRIPTION,
identifier: managedRuleName,
inputParameters: {
MaxPasswordAge: maxPasswordAge,
MinimumPasswordLength: minimumPasswordLength,
PasswordReusePrevention: passwordReusePrevention,
RequireLowercaseCharacters: requireLowercaseCharacters,
RequireNumbers: requireNumbers,
RequireSymbols: requireSymbols,
RequireUppercaseCharacters: requireUppercaseCharacters,
},
maximumExecutionFrequency: props.maximumExecutionFrequency,
});

this.maxPasswordAge = maxPasswordAge;
this.minimumPasswordLength = minimumPasswordLength;
this.passwordReusePrevention = passwordReusePrevention;
this.requireLowercaseCharacters = requireLowercaseCharacters;
this.requireNumbers = requireNumbers;
this.requireSymbols = requireSymbols;
this.requireUppercaseCharacters = requireUppercaseCharacters;

const description = [
`Allows remdiation of of a non-compliant '${managedRuleName}' AWS`,
'Config rule finding.',
].join(' ');

this.remediationPolicy = new ManagedPolicy(this, 'remediation-policy', {
description: description,
path: '/config/',
statements: [
new PolicyStatement({
actions: [
'iam:GetAccountPasswordPolicy',
'iam:UpdateAccountPasswordPolicy',
],
effect: Effect.ALLOW,
resources: [
'*',
],
}),
],
});

this.remediationRole = new Role(this, 'remediation-role', {
assumedBy: new ServicePrincipal('ssm.amazonaws.com'),
description: description,
managedPolicies: [
this.remediationPolicy,
],
});

this.remediationConfiguration = new RemediationConfiguration(this, 'remediation-configuration', {
configRule: this,
staticParameters: {
AutomationAssumeRole: [
this.remediationRole.roleArn,
],
MaxPasswordAge: [
maxPasswordAge,
],
MinimumPasswordLength: [
minimumPasswordLength,
],
PasswordReusePrevention: [
passwordReusePrevention,
],
RequireLowercaseCharacters: [
requireLowercaseCharacters,
],
RequireNumbers: [
requireNumbers,
],
RequireSymbols: [
requireSymbols,
],
RequireUppercaseCharacters: [
requireUppercaseCharacters,
],
},
target: RemediationTarget.automationDocument({
document: AutomationDocument.fromManaged(this, 'remediation-document', IamPasswordPolicy.REMEDIATION_DOCUMENT_NAME),
}),
});
}
}
2 changes: 2 additions & 0 deletions src/config-rules/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './iam-password-policy';
export * from './vpc-default-security-group-closed';
99 changes: 99 additions & 0 deletions src/config-rules/vpc-default-security-group-closed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { ArnFormat, ResourceProps } from 'aws-cdk-lib';
import { ManagedRule, MaximumExecutionFrequency, ResourceType, RuleScope } from 'aws-cdk-lib/aws-config';
import { Effect, ManagedPolicy, PolicyStatement, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
import { IConstruct } from 'constructs';
import { RemediationTarget } from '../config/lib';
import { RemediationConfiguration } from '../config/remediation-configuration';
import { AutomationDocument } from '../ssm';


export interface VpcDefaultSecurityGroupClosedProps extends ResourceProps {
readonly autoRemediation?: boolean;
readonly configRuleName?: string;
readonly description?: string;
readonly maximumExecutionFrequency?: MaximumExecutionFrequency;
}

export class VpcDefaultSecurityGroupClosed extends ManagedRule {
public static readonly DEFAULT_DESCRIPTION: string = [
'Checks if the default security group of any Amazon Virtual Private Cloud',
'(Amazon VPC) does not allow inbound or outbound traffic. The rule is',
'NON_COMPLIANT if the default security group has one or more inbound or',
'outbound traffic rules.',
].join(' ');
public static readonly MANAGED_RULE_NAME: string = 'VPC_DEFAULT_SECURITY_GROUP_CLOSED';
public static readonly REMEDIATION_DOCUMENT_NAME: string = 'AWSConfigRemediation-RemoveVPCDefaultSecurityGroupRules';

public readonly remediationConfiguration: RemediationConfiguration;
public readonly remediationPolicy: ManagedPolicy;
public readonly remediationRole: Role;


public constructor(scope: IConstruct, id: string, props: VpcDefaultSecurityGroupClosedProps) {
const managedRuleName = VpcDefaultSecurityGroupClosed.MANAGED_RULE_NAME;

super(scope, id, {
configRuleName: props.configRuleName,
description: props.description ?? VpcDefaultSecurityGroupClosed.DEFAULT_DESCRIPTION,
identifier: managedRuleName,
maximumExecutionFrequency: props.maximumExecutionFrequency,
ruleScope: RuleScope.fromResource(ResourceType.EC2_SECURITY_GROUP),
});

const description = [
`Allows remdiation of of a non-compliant '${managedRuleName}' AWS`,
'Config rule finding.',
].join(' ');

this.remediationPolicy = new ManagedPolicy(this, 'remediation-policy', {
description: description,
path: '/config/',
statements: [
new PolicyStatement({
actions: [
'ec2:DescribeSecurityGroups',
],
effect: Effect.ALLOW,
resources: [
'*',
],
}),
new PolicyStatement({
actions: [
'ec2:RevokeSecurityGroupEgress',
'ec2:RevokeSecurityGroupIngress',
],
effect: Effect.ALLOW,
resources: [
this.stack.formatArn({
arnFormat: ArnFormat.SLASH_RESOURCE_NAME,
resource: 'security-group',
resourceName: '*',
service: 'ec2',
}),
],
}),
],
});

this.remediationRole = new Role(this, 'remediation-role', {
assumedBy: new ServicePrincipal('ssm.amazonaws.com'),
description: description,
managedPolicies: [
this.remediationPolicy,
],
});
this.remediationConfiguration = new RemediationConfiguration(this, 'remediation-configuration', {
configRule: this,
resourceParameter: 'GroupId',
staticParameters: {
AutomationAssumeRole: [
this.remediationRole.roleArn,
],
},
target: RemediationTarget.automationDocument({
document: AutomationDocument.fromManaged(this, 'remediation-document', VpcDefaultSecurityGroupClosed.REMEDIATION_DOCUMENT_NAME),
}),
});
}
}
2 changes: 2 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './lib';
export * from './remediation-configuration';
1 change: 1 addition & 0 deletions src/config/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './remediation-target';
58 changes: 58 additions & 0 deletions src/config/lib/remediation-target.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { CfnRemediationConfiguration } from 'aws-cdk-lib/aws-config';
import { IConstruct } from 'constructs';
import { IAutomationDocument } from '../../ssm';
import { definedFieldsOrUndefined } from '../../utils/formatting';


export class RemediationTargetType {
static readonly SSM_DOCUMENT: RemediationTargetType = RemediationTargetType.of('SSM_DOCUMENT');

static of(value: string): RemediationTargetType {
return new RemediationTargetType(value);
}


readonly value: string;

private constructor(value: string) {
this.value = value;
}
}

export interface RemediationTargetConfiguration {
readonly controls?: CfnRemediationConfiguration.ExecutionControlsProperty;
readonly targetId: string;
readonly targetType: RemediationTargetType;
readonly targetVersion?: string;
}

export interface IRemediationTarget {
bind(scope: IConstruct): RemediationTargetConfiguration;
}

export interface AutomationDocumentRemediationProps {
readonly concurrencyPercentage?: number;
readonly document: IAutomationDocument;
readonly errorPercentage?: number;
readonly version?: string;
}

export class RemediationTarget {
static automationDocument(props: AutomationDocumentRemediationProps): IRemediationTarget {
return {
bind: (_scope) => {
return {
controls: definedFieldsOrUndefined({
ssmControls: definedFieldsOrUndefined({
concurrentExecutionRatePercentage: props.concurrencyPercentage,
errorPercentage: props.errorPercentage,
}),
}),
targetId: props.document.documentName,
targetType: RemediationTargetType.SSM_DOCUMENT,
targetVersion: props.version,
};
},
};
}
}
Loading

0 comments on commit 5b16174

Please sign in to comment.