💾 Archived View for capsule.adrianhesketh.com › 2022 › 05 › 31 › create-vpc-with-cdk captured on 2023-05-24 at 17:53:09. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2022-06-11)
-=-=-=-=-=-=-
const vpc = new ec2.Vpc(this, "VPC", {})
The default configuration is usually not sufficient to pass security audits, since it doesn't follow AWS best practice.
This post explains what else is required.
The default configuration for VPCs is sensible. You get 1 public, and 1 private subnet per availability zone in the region, but I add in the isolated subnet.
Typically, only load balancers and NAT Gateways should be placed in the public subnets. Lambda functions, Fargate instances etc., should be in the private subnet, where traffic from the Internet can't make direct inbound connections.
CDK automatically sets up NAT Gateways, and all of the network routing required to enable private subnets to make outbound connections to the Internet (e.g. to make outbound API calls).
Use the isolated subnet to hold infrastructure that isn't allowed to connect to the Internet. When combined with VPC endpoints, data held in S3 or DynamoDB can still be processed.
const vpc = new ec2.Vpc(this, "VPC", { subnetConfiguration: [ { name: "public-subnet", subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24, }, { name: "private-subnet", subnetType: ec2.SubnetType.PRIVATE, cidrMask: 24, }, { name: "isolated-subnet", subnetType: ec2.SubnetType.ISOLATED, cidrMask: 24, }, ], // maxAzs: 2, // natGateways: 2, });
If you want to change your range later, you have to delete and recreate the VPC.
If you might need to set up a VPN to another network, contact their team for advice so that your address range doesn't conflict.
In my closest region (Ireland), it costs ($0.048 per hour * 24 hours * 365 days a year / 12 months) = $35.04, per availability zone to run a NAT gateway, and by default, you're likely to need 3.
To control costs, you can reduce the number of zones with the `maxAzs` config parameter.
In my personal projects, I sometimes place instances in the public subnet and set the `natGateways` field to zero. This means there's no running costs for the network, but if I open up an instance to the Internet, it can be attacked easily, since it's not protected in any way.
It's best practice to log API Gateway access, CloudFront requests, VPC flow logs etc.
If you don't enable logging on these services, it will show up in security audits as a finding, since it's part of CIS AWS Foundation Benchmarks [1].
To store VPC flow logs, and other logs, you need a log bucket to put them in.
The default CDK configuration doesn't follow best practice in several areas, and so will be flagged by AWS security tooling, you also need to:
const s3LogBucket = new s3.Bucket(this, "s3LogBucket", { blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, enforceSSL: true, versioned: true, accessControl: s3.BucketAccessControl.LOG_DELIVERY_WRITE, encryption: s3.BucketEncryption.S3_MANAGED, intelligentTieringConfigurations: [ { name: "archive", archiveAccessTierTime: Duration.days(90), deepArchiveAccessTierTime: Duration.days(180), }, ], })
If your service is attacked, you can use flow logs to determine what outbound or inbound IP addresses were used, and also determine the quantity of data extracted etc.
VPC flow logs are required by security tooling like AWS GuardDuty to look for unusual traffic patterns, so you generally need it switched on.
You may need to copy flow logs up outside of the account, since an attacker might delete them.
const vpcFlowLogRole = new iam.Role(this, "vpcFlowLogRole", { assumedBy: new iam.ServicePrincipal("vpc-flow-logs.amazonaws.com"), }) s3LogBucket.grantWrite(vpcFlowLogRole, "sharedVpcFlowLogs/*") // Create flow logs to S3. new ec2.FlowLog(this, "sharedVpcLowLogs", { destination: ec2.FlowLogDestination.toS3(s3LogBucket, "sharedVpcFlowLogs/"), trafficType: ec2.FlowLogTrafficType.ALL, flowLogName: "sharedVpcFlowLogs", resourceType: ec2.FlowLogResourceType.fromVpc(vpc), })
For workloads that use AWS services, VPC endpoints can reduce outbound internet traffic and NAT Gateway costs.
VPC Endpoints improve security, since your data does not mix with public Internet traffic.
CDK sets up all of the required network routing, and the AWS SDK uses the endpoints automatically. No changes are required to program code to benefit from them.
vpc.addGatewayEndpoint("dynamoDBEndpoint", { service: ec2.GatewayVpcEndpointAwsService.DYNAMODB, }) vpc.addGatewayEndpoint("s3Endpoint", { service: ec2.GatewayVpcEndpointAwsService.S3, })
Many organisations require that AWS Lambda functions run inside a VPC to benefit from VPC flow logs.
If you just keep adding new Lambda functions to the VPC, eventually, you'll get the error:
You have exceeded the maximum limit for HyperPlane ENIs for your account
This is because, by default, each Lambda function creates a new security group for itself. This is pointless because the vast majority of the time, the security group is simply the default.
To prevent running out of ENIs, create a default security group and encourage its use.
const noInboundAllOutboundSecurityGroup = new ec2.SecurityGroup(this, "noInboundAllOutboundSecurityGroup", { vpc: vpc, allowAllOutbound: true, description: "No inbound / all outbound", securityGroupName: "noInboundAllOutboundSecurityGroup", }) new CfnOutput(this, "noInboundAllOutboundSecurityGroup", { exportName: "noInboundAllOutboundSecurityGroup", value: noInboundAllOutboundSecurityGroup.securityGroupId, })
Any VPC you create will have default security groups added which allow egress.
These immediately show up in security audits and should be removed, however, CDK can't modify them, so I wrote a program to remove them [2]
CDK's default configuration is OK, but won't pass a security review out of the box, but only a few tweaks are needed to improve things.
Check out the full code at [3]