├── .npmignore ├── inspec ├── inputs.yml ├── inspec.lock ├── controls │ ├── 01_s3_bucket.rb │ └── 02_full_stack_app.rb ├── inspec.yml └── README.md ├── .gitignore ├── jest.config.js ├── bin └── infrastructure-test-examples.ts ├── tsconfig.json ├── lib ├── s3-bucket-stack.ts └── full-stack-app-stack.ts ├── package.json ├── cdk.json ├── LICENSE ├── test ├── s3-bucket-stack.test.ts └── full-stack-app-stack.test.ts └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /inspec/inputs.yml: -------------------------------------------------------------------------------- 1 | # Below is to be uncommented and set with your AWS Custom VPC ID: 2 | # aws_vpc_id: 'vpc-xxxxxxx' 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | 10 | 11 | outputs.json -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /inspec/inspec.lock: -------------------------------------------------------------------------------- 1 | --- 2 | lockfile_version: 1 3 | depends: 4 | - name: inspec-aws 5 | resolved_source: 6 | url: https://github.com/inspec/inspec-aws/archive/master.tar.gz 7 | sha256: f6fcd1c73f1de5ed6d0a68e2f5def0345929b3fd9b478f662adcd7370adfe589 8 | version_constraints: [] 9 | -------------------------------------------------------------------------------- /bin/infrastructure-test-examples.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import * as cdk from '@aws-cdk/core'; 3 | import { FullStackAppStack } from '../lib/full-stack-app-stack'; 4 | import { S3BucketStack } from '../lib/s3-bucket-stack'; 5 | 6 | const app = new cdk.App(); 7 | new FullStackAppStack(app, 'FullStackAppStack'); 8 | new S3BucketStack(app, 'S3BucketStack'); 9 | -------------------------------------------------------------------------------- /inspec/controls/01_s3_bucket.rb: -------------------------------------------------------------------------------- 1 | title "Verify S3 Buckets" 2 | 3 | control "01-s3-bucket" do 4 | impact 1.0 5 | title "Verify S3 Bucket is Private" 6 | 7 | json = inspec.profile.file('outputs.json') 8 | outputs = JSON.parse(json) 9 | 10 | BUCKET_NAME = outputs['FullStackAppStack']['BucketName'] 11 | 12 | describe aws_s3_bucket(BUCKET_NAME) do 13 | it { should exist } 14 | it { should_not be_public } 15 | its('bucket_policy') { should be_empty } 16 | its('bucket_acl.count') { should eq 1 } 17 | it { should have_default_encryption_enabled } 18 | end 19 | 20 | end -------------------------------------------------------------------------------- /inspec/inspec.yml: -------------------------------------------------------------------------------- 1 | name: inspec 2 | title: AWS InSpec Profile 3 | maintainer: Jenna Pederson 4 | copyright: Jenna Pederson 5 | copyright_email: you@example.com 6 | license: Apache-2.0 7 | summary: An InSpec Compliance Profile For AWS 8 | version: 0.1.0 9 | inspec_version: '~> 4' 10 | inputs: 11 | - name: aws_vpc_id 12 | required: false 13 | # Below is deliberately left as a default empty string to allow the profile to run when this is not provided. 14 | # Please see the README for more details. 15 | value: '' 16 | description: 'Optional Custom AWS VPC Id' 17 | depends: 18 | - name: inspec-aws 19 | url: https://github.com/inspec/inspec-aws/archive/master.tar.gz 20 | supports: 21 | - platform: aws 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "cdk.out" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /lib/s3-bucket-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from '@aws-cdk/core'; 2 | import * as s3 from '@aws-cdk/aws-s3'; 3 | 4 | export class S3BucketStack extends cdk.Stack { 5 | constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { 6 | super(scope, id, props); 7 | 8 | const bucketName = new cdk.CfnParameter(this, "bucketName", { 9 | type: "String", 10 | description: "The name of the S3 bucket to create. Must be globally unique."}); 11 | 12 | new s3.Bucket(this, 'cdk-test-bucket', { 13 | bucketName: bucketName.valueAsString, 14 | blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, 15 | encryption: s3.BucketEncryption.KMS_MANAGED 16 | }); 17 | 18 | new cdk.CfnOutput(this, 'BucketName', { 19 | value: bucketName.valueAsString 20 | }); 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "infrastructure-test-examples", 3 | "version": "0.1.0", 4 | "license": "MIT", 5 | "repository": "github:jennapederson/infrastructure-test-examples", 6 | "bin": { 7 | "infrastructure-test-examples": "bin/infrastructure-test-examples.js" 8 | }, 9 | "scripts": { 10 | "build": "tsc", 11 | "watch": "tsc -w", 12 | "test": "jest", 13 | "cdk": "cdk" 14 | }, 15 | "devDependencies": { 16 | "aws-cdk": "1.114.0", 17 | "@aws-cdk/assert": "1.114.0", 18 | "@types/jest": "^26.0.10", 19 | "@types/node": "10.17.27", 20 | "jest": "^26.4.2", 21 | "ts-jest": "^26.2.0", 22 | "ts-node": "^9.0.0", 23 | "typescript": "~3.9.7" 24 | }, 25 | "dependencies": { 26 | "@aws-cdk/aws-rds": "^1.114.0", 27 | "@aws-cdk/core": "1.114.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/infrastructure-test-examples.ts", 3 | "context": { 4 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 5 | "@aws-cdk/core:enableStackNameDuplicates": "true", 6 | "aws-cdk:enableDiffNoFail": "true", 7 | "@aws-cdk/core:stackRelativeExports": "true", 8 | "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true, 9 | "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true, 10 | "@aws-cdk/aws-kms:defaultKeyPolicies": true, 11 | "@aws-cdk/aws-s3:grantWriteWithoutAcl": true, 12 | "@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": true, 13 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 14 | "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, 15 | "@aws-cdk/aws-lambda:recognizeVersionProps": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jenna Pederson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /test/s3-bucket-stack.test.ts: -------------------------------------------------------------------------------- 1 | import { expect as expectCDK, countResources, haveResource, haveOutput } from '@aws-cdk/assert'; 2 | import '@aws-cdk/assert/jest'; 3 | import * as cdk from '@aws-cdk/core'; 4 | import * as s3BucketStack from '../lib/s3-bucket-stack'; 5 | 6 | test('stack creates an S3 bucket with server-side encryption enabled', () => { 7 | const app = new cdk.App(); 8 | 9 | const stack = new s3BucketStack.S3BucketStack(app, 'S3BucketStack'); 10 | 11 | expectCDK(stack).to(haveResource('AWS::S3::Bucket', { 12 | BucketName: { 13 | "Ref": "bucketName" 14 | }, 15 | PublicAccessBlockConfiguration: { 16 | BlockPublicAcls: true, 17 | BlockPublicPolicy: true, 18 | IgnorePublicAcls: true, 19 | RestrictPublicBuckets: true 20 | }, 21 | BucketEncryption: { 22 | ServerSideEncryptionConfiguration: [ 23 | { 24 | ServerSideEncryptionByDefault: { 25 | SSEAlgorithm: "aws:kms" 26 | } 27 | } 28 | ] 29 | }, 30 | })); 31 | 32 | expectCDK(stack).to(countResources('AWS::S3::Bucket', 1)); 33 | }); 34 | 35 | test('stack outputs BucketName', () => { 36 | const app = new cdk.App(); 37 | 38 | const stack = new s3BucketStack.S3BucketStack(app, 'S3BucketStack'); 39 | 40 | expectCDK(stack).to(haveOutput({ 41 | outputName: "BucketName", 42 | outputValue: { 43 | "Ref": "bucketName" 44 | }, 45 | })); 46 | }); -------------------------------------------------------------------------------- /inspec/controls/02_full_stack_app.rb: -------------------------------------------------------------------------------- 1 | title "Verify full stack webapp" 2 | 3 | control "02-full-stack" do 4 | impact 1.0 5 | title "Verify full stack webapp" 6 | 7 | json = inspec.profile.file('outputs.json') 8 | outputs = JSON.parse(json) 9 | 10 | INSTANCE_ID = outputs['FullStackAppStack']['InstanceId'] 11 | WEB_SECURITY_GROUP_ID = outputs['FullStackAppStack']['WebSecurityGroupId'] 12 | DB_INSTANCE_IDENTIFIER = outputs['FullStackAppStack']['DbInstanceIdentifier'] 13 | DB_SECURITY_GROUP_ID = outputs['FullStackAppStack']['DbSecurityGroupId'] 14 | 15 | describe aws_ec2_instance(INSTANCE_ID) do 16 | it { should be_running } 17 | its('instance_type') { should eq 't2.micro' } 18 | its('image_id') { should eq 'ami-0dc2d3e4c0f9ebd18' } 19 | end 20 | 21 | describe aws_security_group(group_id: WEB_SECURITY_GROUP_ID) do 22 | it { should exist } 23 | it { should allow_in(port: 80, protocol: 'tcp') } 24 | it { should allow_in(port: 443, protocol: 'tcp') } 25 | it { should allow_in(port: 22, protocol: 'tcp') } 26 | it { should allow_out(ipv4_range: '0.0.0.0/0') } 27 | end 28 | 29 | describe aws_security_group(group_id: DB_SECURITY_GROUP_ID) do 30 | it { should exist } 31 | it { should allow_in(port: 5432, protocol: 'tcp') } 32 | end 33 | 34 | describe aws_rds_instance(db_instance_identifier: DB_INSTANCE_IDENTIFIER) do 35 | its('engine') { should eq 'postgres' } 36 | its('engine_version') { should eq '12.7' } 37 | its('db_instance_class') { should eq 'db.t2.small' } 38 | end 39 | 40 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Infrastructure Test Examples 2 | 3 | This repository contains examples for testing your infrastructure code written with the AWS CDK. Unit tests are written with the [Jest testing framework](https://jestjs.io/). Integration tests are written with the [InSpec testing and auditing framework](https://docs.chef.io/inspec/). 4 | 5 | ## Getting Started 6 | 7 | 1. Install the AWS CDK 8 | `npm install -g aws-cdk` 9 | You can find more CDK setup details [here](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html). The examples in this project are written with Typescript. 10 | 1. Install InSpec using [these instructions](https://docs.chef.io/inspec/install/#install-chef-inspec). 11 | 1. Install dependencies 12 | `npm install` 13 | 1. Run unit tests 14 | `npm run build && npm run test` 15 | 1. Setup your AWS credentials for InSpec 16 | Following the instructions here, you can use environment variables or a profile in `~/.aws/credentials`. 17 | 1. Synthesize 18 | `cdk synth` 19 | 1. Deploy your changes 20 | `cdk deploy --all --parameters FullStackAppStack:keyPairName= --parameters S3BucketStack:bucketName= --outputs-file inspec/files/outputs.json` 21 | 1. Run InSpec tests 22 | `inspec exec inspec -t aws://` 23 | If you get an error about the lockfile, delete `inspec/inspec.lock` and rerun the command. 24 | 25 | ## Other useful commands 26 | 27 | * `npm run build` compile typescript to js 28 | * `npm run watch` watch for changes and compile 29 | * `npm run test` perform the jest unit tests 30 | * `cdk deploy` deploy this stack to your default AWS account/region 31 | * `cdk diff` compare deployed stack with current state 32 | * `cdk synth` emits the synthesized CloudFormation template 33 | * `cdk destroy` destroys the stack and all associated resources 34 | * `inspec exec inspec -t aws://` runs the inspec integration tests for the inspec test profile 35 | -------------------------------------------------------------------------------- /lib/full-stack-app-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from '@aws-cdk/core'; 2 | import * as ec2 from '@aws-cdk/aws-ec2'; 3 | import * as rds from '@aws-cdk/aws-rds'; 4 | 5 | export class FullStackAppStack extends cdk.Stack { 6 | constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { 7 | super(scope, id, props); 8 | 9 | const keyPairName = new cdk.CfnParameter(this, "keyPairName", { 10 | type: "String", 11 | description: "The name of an existing Amazon EC2 key pair in this region to use to SSH into the Amazon EC2 instances."}); 12 | 13 | const vpc = new ec2.Vpc(this, 'full-stack-app-vpc', { 14 | subnetConfiguration: [ 15 | { 16 | name: 'full-stack-app-public-subnet', 17 | subnetType: ec2.SubnetType.PUBLIC, 18 | cidrMask: 24, 19 | }, 20 | { 21 | name: 'full-stack-app-isolated-subnet', 22 | subnetType: ec2.SubnetType.ISOLATED, 23 | cidrMask: 24, 24 | }, 25 | ], 26 | }); 27 | 28 | const ami = new ec2.AmazonLinuxImage({ 29 | generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2, 30 | cpuType: ec2.AmazonLinuxCpuType.X86_64 31 | }); 32 | 33 | const webAppSecurityGroup = new ec2.SecurityGroup(this, 'full-stack-app-web-security-group', { 34 | vpc, 35 | description: 'Allow HTTP/HTTPS and SSH inbound and outbound traffic', 36 | allowAllOutbound: true 37 | }); 38 | webAppSecurityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH Access'); 39 | webAppSecurityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP Access'); 40 | webAppSecurityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS Access') 41 | 42 | const webAppInstance = new ec2.Instance(this, 'full-stack-app-instance', { 43 | vpc, 44 | vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, 45 | instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO), 46 | machineImage: ami, 47 | securityGroup: webAppSecurityGroup, 48 | keyName: keyPairName.valueAsString 49 | }); 50 | 51 | const eip = new ec2.CfnEIP(this, 'full-stack-app-eip', { 52 | instanceId: webAppInstance.instanceId, 53 | }); 54 | cdk.Tags.of(eip).add('Name', 'full-stack-app-eip'); 55 | 56 | const webAppDatabase = new rds.DatabaseInstance(this, 'full-stack-app-rds', { 57 | vpc, 58 | vpcSubnets: { 59 | subnetType: ec2.SubnetType.ISOLATED, 60 | }, 61 | instanceIdentifier: 'full-stack-app-rds', 62 | engine: rds.DatabaseInstanceEngine.postgres({ 63 | version:rds.PostgresEngineVersion.VER_12_7, 64 | }), 65 | allocatedStorage: 5, 66 | instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.SMALL), 67 | }); 68 | cdk.Tags.of(webAppDatabase).add('Name', 'full-stack-app-rds'); 69 | 70 | webAppDatabase.connections.allowFrom(webAppInstance, ec2.Port.tcp(5432)); 71 | 72 | new cdk.CfnOutput(this, 'WebAppDatabaseEndpoint', { 73 | value: webAppDatabase.instanceEndpoint.hostname, 74 | }); 75 | 76 | new cdk.CfnOutput(this, 'WebServerPublicDNS', { 77 | value: webAppInstance.instancePublicDnsName 78 | }); 79 | 80 | new cdk.CfnOutput(this, 'WebsiteURL', { 81 | value: `https://${eip.ref}` 82 | }); 83 | 84 | new cdk.CfnOutput(this, 'InstanceId', { 85 | value: webAppInstance.instanceId 86 | }); 87 | 88 | new cdk.CfnOutput(this, 'DbInstanceIdentifier', { 89 | value: webAppDatabase.instanceIdentifier 90 | }); 91 | 92 | new cdk.CfnOutput(this, 'WebSecurityGroupId', { 93 | value: webAppSecurityGroup.securityGroupId 94 | }); 95 | 96 | new cdk.CfnOutput(this, 'DbSecurityGroupId', { 97 | value: webAppDatabase.connections.securityGroups[0].securityGroupId 98 | }); 99 | 100 | 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /test/full-stack-app-stack.test.ts: -------------------------------------------------------------------------------- 1 | import { expect as expectCDK, countResources, haveResource, haveOutput } from '@aws-cdk/assert'; 2 | import '@aws-cdk/assert/jest'; 3 | import * as cdk from '@aws-cdk/core'; 4 | import * as fullStackAppStack from '../lib/full-stack-app-stack'; 5 | 6 | test('stack creates an EC2 instance with elastic IP', () => { 7 | const app = new cdk.App(); 8 | 9 | const stack = new fullStackAppStack.FullStackAppStack(app, 'FullStackAppStack'); 10 | 11 | expectCDK(stack).to(haveResource('AWS::EC2::Instance', { 12 | KeyName: { 13 | "Ref": "keyPairName" 14 | }, 15 | InstanceType: "t2.micro" 16 | })); 17 | 18 | expectCDK(stack).to(haveResource('AWS::EC2::EIP', { 19 | Tags: [ 20 | { 21 | "Key": "Name", 22 | "Value": "full-stack-app-eip" 23 | } 24 | ] 25 | })); 26 | 27 | }); 28 | 29 | test('stack creates an RDS instance', () => { 30 | const app = new cdk.App(); 31 | 32 | const stack = new fullStackAppStack.FullStackAppStack(app, 'FullStackAppStack'); 33 | 34 | expectCDK(stack).to(haveResource('AWS::RDS::DBInstance', { 35 | DBInstanceClass: "db.t2.small", 36 | Engine: "postgres", 37 | EngineVersion: "12.7", 38 | Tags: [ 39 | { 40 | "Key": "Name", 41 | "Value": "full-stack-app-rds" 42 | } 43 | ] 44 | })); 45 | }); 46 | 47 | test('stack outputs WebAppDatabaseEndpoint', () => { 48 | const app = new cdk.App(); 49 | 50 | const stack = new fullStackAppStack.FullStackAppStack(app, 'FullStackAppStack'); 51 | 52 | expectCDK(stack).to(haveOutput({ 53 | outputName: "WebAppDatabaseEndpoint", 54 | outputValue: { 55 | "Fn::GetAtt": [ 56 | "fullstackapprdsFBF09CFA", 57 | "Endpoint.Address" 58 | ] 59 | }, 60 | })); 61 | }); 62 | 63 | test('stack outputs WebServerPublicDNS', () => { 64 | const app = new cdk.App(); 65 | 66 | const stack = new fullStackAppStack.FullStackAppStack(app, 'FullStackAppStack'); 67 | 68 | expectCDK(stack).to(haveOutput({ 69 | outputName: "WebServerPublicDNS", 70 | outputValue: { 71 | "Fn::GetAtt": [ 72 | "fullstackappinstanceEF5DDB6A", 73 | "PublicDnsName" 74 | ] 75 | }, 76 | })); 77 | }); 78 | 79 | test('stack outputs WebsiteURL', () => { 80 | const app = new cdk.App(); 81 | 82 | const stack = new fullStackAppStack.FullStackAppStack(app, 'FullStackAppStack'); 83 | 84 | expectCDK(stack).to(haveOutput({ 85 | outputName: "WebsiteURL", 86 | outputValue: { 87 | "Fn::Join": [ 88 | "", 89 | [ 90 | "https://", 91 | { 92 | "Ref": "fullstackappeip" 93 | } 94 | ] 95 | ] 96 | }, 97 | })); 98 | 99 | }); 100 | 101 | test('stack outputs InstanceId', () => { 102 | const app = new cdk.App(); 103 | 104 | const stack = new fullStackAppStack.FullStackAppStack(app, 'FullStackAppStack'); 105 | 106 | expectCDK(stack).to(haveOutput({ 107 | outputName: "InstanceId", 108 | outputValue: { 109 | "Ref": "fullstackappinstanceEF5DDB6A" 110 | }, 111 | })); 112 | }); 113 | 114 | test('stack outputs WebSecurityGroupId', () => { 115 | const app = new cdk.App(); 116 | 117 | const stack = new fullStackAppStack.FullStackAppStack(app, 'FullStackAppStack'); 118 | 119 | expectCDK(stack).to(haveOutput({ 120 | outputName: "WebSecurityGroupId", 121 | outputValue: { 122 | "Fn::GetAtt": [ 123 | "fullstackappwebsecuritygroup8D23C35D", 124 | "GroupId" 125 | ] 126 | }, 127 | })); 128 | }); 129 | 130 | test('stack outputs DbInstanceIdentifier', () => { 131 | const app = new cdk.App(); 132 | 133 | const stack = new fullStackAppStack.FullStackAppStack(app, 'FullStackAppStack'); 134 | 135 | expectCDK(stack).to(haveOutput({ 136 | outputName: "DbInstanceIdentifier", 137 | outputValue: { 138 | "Ref": "fullstackapprdsFBF09CFA" 139 | }, 140 | })); 141 | }); 142 | 143 | test('stack outputs DbSecurityGroupId', () => { 144 | const app = new cdk.App(); 145 | 146 | const stack = new fullStackAppStack.FullStackAppStack(app, 'FullStackAppStack'); 147 | 148 | expectCDK(stack).to(haveOutput({ 149 | outputName: "DbSecurityGroupId", 150 | outputValue: { 151 | "Fn::GetAtt": [ 152 | "fullstackapprdsSecurityGroup50856175", 153 | "GroupId" 154 | ] 155 | }, 156 | })); 157 | }); -------------------------------------------------------------------------------- /inspec/README.md: -------------------------------------------------------------------------------- 1 | # Example InSpec Profile For AWS 2 | 3 | This example shows the implementation of an InSpec profile for AWS. 4 | 5 | ## Create a profile 6 | 7 | ``` 8 | $ inspec init profile --platform aws my-profile 9 | 10 | ─────────────────────────── InSpec Code Generator ─────────────────────────── 11 | 12 | Creating new profile at /Users/spaterson/my-profile 13 | • Creating directory libraries 14 | • Creating file README.md 15 | • Creating directory controls 16 | • Creating file controls/example.rb 17 | • Creating file inspec.yml 18 | • Creating file inputs.yml 19 | • Creating file libraries/.gitkeep 20 | 21 | ``` 22 | 23 | ## Optionally update `inputs.yml` to point to your custom VPC 24 | 25 | ``` 26 | aws_vpc_id: 'custom-vpc-id' 27 | ``` 28 | 29 | The related control will simply be skipped if this is not provided. See the [InSpec DSL documentation](https://docs.chef.io/inspec/dsl_inspec/) for more details on conditional execution using `only_if`. 30 | 31 | ## Run the tests 32 | 33 | ### With a VPC Identifier 34 | 35 | With a supplied VPC identifier in `inputs.yml` both of the example controls will run. The 'aws-single-vpc-exists-check' control will only check for a VPC identifier in the currently configured AWS SDK region e.g. `eu-west-2` in the below: 36 | 37 | ``` 38 | $ cd my-profile/ 39 | $ inspec exec . -t aws:// --input-file=inputs.yml 40 | 41 | Profile: AWS InSpec Profile (my-profile) 42 | Version: 0.1.0 43 | Target: aws://eu-west-2 44 | 45 | ✔ aws-single-vpc-exists-check: Check to see if custom VPC exists. 46 | ✔ VPC vpc-1ea06476 should exist 47 | ✔ aws-vpcs-check: Check in all the VPCs for default sg not allowing 22 inwards 48 | ✔ EC2 Security Group sg-067cd21e928c3a2f1 should allow in {:port=>22} 49 | ✔ EC2 Security Group sg-9bb3b9f3 should allow in {:port=>22} 50 | ✔ aws-vpcs-multi-region-status-check: Check AWS VPCs in all regions have status "available" 51 | ✔ VPC vpc-6458b70d in eu-north-1 should exist 52 | ✔ VPC vpc-6458b70d in eu-north-1 should be available 53 | ✔ VPC vpc-8d1390e5 in ap-south-1 should exist 54 | ✔ VPC vpc-8d1390e5 in ap-south-1 should be available 55 | ✔ VPC vpc-07a71d6e in eu-west-3 should exist 56 | ✔ VPC vpc-07a71d6e in eu-west-3 should be available 57 | ✔ VPC vpc-021630e2e767412b5 in eu-west-2 should exist 58 | ✔ VPC vpc-021630e2e767412b5 in eu-west-2 should be available 59 | ✔ VPC vpc-1ea06476 in eu-west-2 should exist 60 | ✔ VPC vpc-1ea06476 in eu-west-2 should be available 61 | ✔ VPC vpc-169dee70 in eu-west-1 should exist 62 | ✔ VPC vpc-169dee70 in eu-west-1 should be available 63 | ✔ VPC vpc-01ac7ba0be447a1c4 in eu-west-1 should exist 64 | ✔ VPC vpc-01ac7ba0be447a1c4 in eu-west-1 should be available 65 | ✔ VPC vpc-09ff83d71da9d2b6e in eu-west-1 should exist 66 | ✔ VPC vpc-09ff83d71da9d2b6e in eu-west-1 should be available 67 | ✔ VPC vpc-0ebccac2337a90f13 in eu-west-1 should exist 68 | ✔ VPC vpc-0ebccac2337a90f13 in eu-west-1 should be available 69 | ✔ VPC vpc-c2a53da4 in eu-west-1 should exist 70 | ✔ VPC vpc-c2a53da4 in eu-west-1 should be available 71 | ✔ VPC vpc-4fb3f127 in ap-northeast-2 should exist 72 | ✔ VPC vpc-4fb3f127 in ap-northeast-2 should be available 73 | ✔ VPC vpc-0804856f in ap-northeast-1 should exist 74 | ✔ VPC vpc-0804856f in ap-northeast-1 should be available 75 | ✔ VPC vpc-ccb917ab in sa-east-1 should exist 76 | ✔ VPC vpc-ccb917ab in sa-east-1 should be available 77 | ✔ VPC vpc-0afcc60c70a30a615 in ca-central-1 should exist 78 | ✔ VPC vpc-0afcc60c70a30a615 in ca-central-1 should be available 79 | ✔ VPC vpc-20a25048 in ca-central-1 should exist 80 | ✔ VPC vpc-20a25048 in ca-central-1 should be available 81 | ✔ VPC vpc-5896143f in ap-southeast-1 should exist 82 | ✔ VPC vpc-5896143f in ap-southeast-1 should be available 83 | ✔ VPC vpc-47972220 in ap-southeast-2 should exist 84 | ✔ VPC vpc-47972220 in ap-southeast-2 should be available 85 | ✔ VPC vpc-071b6f0c69d1d0311 in eu-central-1 should exist 86 | ✔ VPC vpc-071b6f0c69d1d0311 in eu-central-1 should be available 87 | ✔ VPC vpc-807dfdeb in eu-central-1 should exist 88 | ✔ VPC vpc-807dfdeb in eu-central-1 should be available 89 | ✔ VPC vpc-0be54a71311bc362d in eu-central-1 should exist 90 | ✔ VPC vpc-0be54a71311bc362d in eu-central-1 should be available 91 | ✔ VPC vpc-f060cd8b in us-east-1 should exist 92 | ✔ VPC vpc-f060cd8b in us-east-1 should be available 93 | ✔ VPC vpc-0c3a7e116c58d714b in us-east-1 should exist 94 | ✔ VPC vpc-0c3a7e116c58d714b in us-east-1 should be available 95 | ✔ VPC vpc-047bff6c in us-east-2 should exist 96 | ✔ VPC vpc-047bff6c in us-east-2 should be available 97 | ✔ VPC vpc-93dd6ef4 in us-west-1 should exist 98 | ✔ VPC vpc-93dd6ef4 in us-west-1 should be available 99 | ✔ VPC vpc-2c0a6a55 in us-west-2 should exist 100 | ✔ VPC vpc-2c0a6a55 in us-west-2 should be available 101 | 102 | 103 | Profile: Amazon Web Services Resource Pack (inspec-aws) 104 | Version: 0.1.0 105 | Target: aws://eu-west-2 106 | 107 | No tests executed. 108 | 109 | Profile Summary: 3 successful controls, 0 control failures, 0 controls skipped 110 | Test Summary: 53 successful, 0 failures, 0 skipped 111 | ``` 112 | 113 | 114 | ### Without Supplying a VPC Identifier 115 | 116 | If no VPC identifier is supplied, the 'aws-single-vpc-exists-check' control is skipped and the other control runs. The `inputs.yml` file does not have to be specified to InSpec in this case. 117 | 118 | ``` 119 | $ cd my-profile/ 120 | $ inspec exec . -t aws:// 121 | 122 | Profile: AWS InSpec Profile (my-profile) 123 | Version: 0.1.0 124 | Target: aws://eu-west-2 125 | 126 | ↺ aws-single-vpc-exists-check: Check to see if custom VPC exists. 127 | ↺ Skipped control due to only_if condition. 128 | ✔ aws-vpcs-check: Check in all the VPCs for default sg not allowing 22 inwards 129 | ✔ EC2 Security Group sg-067cd21e928c3a2f1 should allow in {:port=>22} 130 | ✔ EC2 Security Group sg-9bb3b9f3 should allow in {:port=>22} 131 | ✔ aws-vpcs-multi-region-status-check: Check AWS VPCs in all regions have status "available" 132 | ✔ VPC vpc-6458b70d in eu-north-1 should exist 133 | ✔ VPC vpc-6458b70d in eu-north-1 should be available 134 | ✔ VPC vpc-8d1390e5 in ap-south-1 should exist 135 | ✔ VPC vpc-8d1390e5 in ap-south-1 should be available 136 | ✔ VPC vpc-07a71d6e in eu-west-3 should exist 137 | ✔ VPC vpc-07a71d6e in eu-west-3 should be available 138 | ✔ VPC vpc-021630e2e767412b5 in eu-west-2 should exist 139 | ✔ VPC vpc-021630e2e767412b5 in eu-west-2 should be available 140 | ✔ VPC vpc-1ea06476 in eu-west-2 should exist 141 | ✔ VPC vpc-1ea06476 in eu-west-2 should be available 142 | ✔ VPC vpc-169dee70 in eu-west-1 should exist 143 | ✔ VPC vpc-169dee70 in eu-west-1 should be available 144 | ✔ VPC vpc-01ac7ba0be447a1c4 in eu-west-1 should exist 145 | ✔ VPC vpc-01ac7ba0be447a1c4 in eu-west-1 should be available 146 | ✔ VPC vpc-09ff83d71da9d2b6e in eu-west-1 should exist 147 | ✔ VPC vpc-09ff83d71da9d2b6e in eu-west-1 should be available 148 | ✔ VPC vpc-0ebccac2337a90f13 in eu-west-1 should exist 149 | ✔ VPC vpc-0ebccac2337a90f13 in eu-west-1 should be available 150 | ✔ VPC vpc-c2a53da4 in eu-west-1 should exist 151 | ✔ VPC vpc-c2a53da4 in eu-west-1 should be available 152 | ✔ VPC vpc-4fb3f127 in ap-northeast-2 should exist 153 | ✔ VPC vpc-4fb3f127 in ap-northeast-2 should be available 154 | ✔ VPC vpc-0804856f in ap-northeast-1 should exist 155 | ✔ VPC vpc-0804856f in ap-northeast-1 should be available 156 | ✔ VPC vpc-ccb917ab in sa-east-1 should exist 157 | ✔ VPC vpc-ccb917ab in sa-east-1 should be available 158 | ✔ VPC vpc-0afcc60c70a30a615 in ca-central-1 should exist 159 | ✔ VPC vpc-0afcc60c70a30a615 in ca-central-1 should be available 160 | ✔ VPC vpc-20a25048 in ca-central-1 should exist 161 | ✔ VPC vpc-20a25048 in ca-central-1 should be available 162 | ✔ VPC vpc-5896143f in ap-southeast-1 should exist 163 | ✔ VPC vpc-5896143f in ap-southeast-1 should be available 164 | ✔ VPC vpc-47972220 in ap-southeast-2 should exist 165 | ✔ VPC vpc-47972220 in ap-southeast-2 should be available 166 | ✔ VPC vpc-071b6f0c69d1d0311 in eu-central-1 should exist 167 | ✔ VPC vpc-071b6f0c69d1d0311 in eu-central-1 should be available 168 | ✔ VPC vpc-807dfdeb in eu-central-1 should exist 169 | ✔ VPC vpc-807dfdeb in eu-central-1 should be available 170 | ✔ VPC vpc-0be54a71311bc362d in eu-central-1 should exist 171 | ✔ VPC vpc-0be54a71311bc362d in eu-central-1 should be available 172 | ✔ VPC vpc-f060cd8b in us-east-1 should exist 173 | ✔ VPC vpc-f060cd8b in us-east-1 should be available 174 | ✔ VPC vpc-0c3a7e116c58d714b in us-east-1 should exist 175 | ✔ VPC vpc-0c3a7e116c58d714b in us-east-1 should be available 176 | ✔ VPC vpc-047bff6c in us-east-2 should exist 177 | ✔ VPC vpc-047bff6c in us-east-2 should be available 178 | ✔ VPC vpc-93dd6ef4 in us-west-1 should exist 179 | ✔ VPC vpc-93dd6ef4 in us-west-1 should be available 180 | ✔ VPC vpc-2c0a6a55 in us-west-2 should exist 181 | ✔ VPC vpc-2c0a6a55 in us-west-2 should be available 182 | 183 | 184 | Profile: Amazon Web Services Resource Pack (inspec-aws) 185 | Version: 0.1.0 186 | Target: aws://eu-west-2 187 | 188 | No tests executed. 189 | 190 | Profile Summary: 2 successful controls, 0 control failures, 1 control skipped 191 | Test Summary: 52 successful, 0 failures, 1 skipped 192 | ``` 193 | --------------------------------------------------------------------------------