diff --git a/dist/fargate.d.ts b/dist/fargate.d.ts index 3700814..55fe0b6 100644 --- a/dist/fargate.d.ts +++ b/dist/fargate.d.ts @@ -3,6 +3,7 @@ import * as cdk from 'aws-cdk-lib'; import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; import * as ecr from 'aws-cdk-lib/aws-ecr'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as acm from 'aws-cdk-lib/aws-certificatemanager'; import * as route53 from 'aws-cdk-lib/aws-route53'; import { StackConfig } from './configuration'; @@ -35,6 +36,7 @@ interface FargateServiceProps extends cdk.NestedStackProps { repository?: ecr.IRepository; taskConfiguration: TaskConfiguration; image?: ecs.ContainerImage; + securityGroup?: ec2.ISecurityGroup; } /** * Generate a fargate service that can be attached to a cluster. This service will include its own diff --git a/dist/fargate.js b/dist/fargate.js index c028626..04c4131 100644 --- a/dist/fargate.js +++ b/dist/fargate.js @@ -33,6 +33,7 @@ const iam = __importStar(require("aws-cdk-lib/aws-iam")); const route53 = __importStar(require("aws-cdk-lib/aws-route53")); const targets = __importStar(require("aws-cdk-lib/aws-route53-targets")); const route53_1 = require("./route53"); +const security_group_1 = require("./security-group"); // Default docker image to use const DEFAULT_IMAGE = 'nginxdemos/hello:latest'; /** @@ -47,7 +48,7 @@ const DEFAULT_IMAGE = 'nginxdemos/hello:latest'; class FargateService extends cdk.NestedStack { constructor(scope, id, props) { super(scope, id, props); - const { healthCheckPath = '/health-check', imageVersion = 'latest', subDomainIncludingDot = '', stack, cluster, certificate, zone, repository, taskConfiguration, } = props; + const { healthCheckPath = '/health-check', imageVersion = 'latest', subDomainIncludingDot = '', stack, cluster, certificate, zone, repository, taskConfiguration, securityGroup: defaultSecurityGroup, } = props; // Compile secrets into list of mapped ecs.Secrets const secrets = {}; const secretValues = taskConfiguration.secrets; @@ -66,8 +67,14 @@ class FargateService extends cdk.NestedStack { (repository && ecs.ContainerImage.fromEcrRepository(repository, imageVersion)) || ecs.ContainerImage.fromRegistry(DEFAULT_IMAGE); + // Scaffold default security group if not provided + let securityGroup = defaultSecurityGroup; + if (!securityGroup) { + const groupStack = new security_group_1.SecurityGroup(this, stack.getResourceID('SecurityGroup'), { vpc: cluster.vpc }); + securityGroup = groupStack.securityGroup; + } const desiredCount = taskConfiguration?.desiredCount || 1; - this.service = new ecs_patterns.ApplicationLoadBalancedFargateService(this, stack.getResourceID('AdminService'), { + this.service = new ecs_patterns.ApplicationLoadBalancedFargateService(this, stack.getResourceID('FargateService'), { assignPublicIp: true, cluster: cluster, certificate: certificate, @@ -85,6 +92,7 @@ class FargateService extends cdk.NestedStack { subnetType: ec2.SubnetType.PUBLIC, onePerAz: true, }, + securityGroups: [securityGroup], }); // Setup AutoScaling policy if (taskConfiguration.autoScalingCpuTarget) { diff --git a/dist/index.d.ts b/dist/index.d.ts index 4c0b41e..c7711c6 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -8,6 +8,7 @@ export * from './helper'; export * from './ipv6vpc'; export * from './rds'; export * from './route53'; +export * from './security-group'; export * from './s3'; export * from './vpc'; export * from './website-distribution'; diff --git a/dist/index.js b/dist/index.js index 04253de..bb858ba 100644 --- a/dist/index.js +++ b/dist/index.js @@ -24,6 +24,7 @@ __exportStar(require("./helper"), exports); __exportStar(require("./ipv6vpc"), exports); __exportStar(require("./rds"), exports); __exportStar(require("./route53"), exports); +__exportStar(require("./security-group"), exports); __exportStar(require("./s3"), exports); __exportStar(require("./vpc"), exports); __exportStar(require("./website-distribution"), exports); diff --git a/dist/rds.js b/dist/rds.js index d36c5db..5832be5 100644 --- a/dist/rds.js +++ b/dist/rds.js @@ -92,6 +92,8 @@ class PostgresInstanceWithBastion extends PostgresInstance { // Create a security group for the bastion host const bastionSecurityGroup = new ec2.SecurityGroup(this, stack.getResourceID('BastionSecurityGroup'), { vpc, + allowAllOutbound: true, + allowAllIpv6Outbound: true, }); // Allow SSH access from anywhere bastionSecurityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22)); diff --git a/dist/security-group.d.ts b/dist/security-group.d.ts new file mode 100644 index 0000000..42e470e --- /dev/null +++ b/dist/security-group.d.ts @@ -0,0 +1,14 @@ +import { Construct } from 'constructs'; +import * as cdk from 'aws-cdk-lib'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +export interface SecurityGroupProps extends cdk.NestedStackProps { + vpc: ec2.IVpc; + incomingPorts?: number[]; +} +/** + * Default security group suitable for a basic ECS service + */ +export declare class SecurityGroup extends cdk.NestedStack { + readonly securityGroup: ec2.ISecurityGroup; + constructor(scope: Construct, id: string, props: SecurityGroupProps); +} diff --git a/dist/security-group.js b/dist/security-group.js new file mode 100644 index 0000000..aaf6cb3 --- /dev/null +++ b/dist/security-group.js @@ -0,0 +1,50 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SecurityGroup = void 0; +const cdk = __importStar(require("aws-cdk-lib")); +const ec2 = __importStar(require("aws-cdk-lib/aws-ec2")); +/** + * Default security group suitable for a basic ECS service + */ +class SecurityGroup extends cdk.NestedStack { + constructor(scope, id, props) { + super(scope, id, props); + const { vpc, incomingPorts } = props; + this.securityGroup = new ec2.SecurityGroup(this, `${id}Group`, { + vpc, + description: 'Allows HTTP/HTTPS ingress and all egress traffic', + allowAllOutbound: true, + allowAllIpv6Outbound: true, + }); + const portsToOpen = incomingPorts || [80, 443]; + // Open all selected ports + portsToOpen.forEach((port) => { + this.securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(port), `Allow ${port} from anywhere (IPv4)`); + this.securityGroup.addIngressRule(ec2.Peer.anyIpv6(), ec2.Port.tcp(port), `Allow ${port} from anywhere (IPv6)`); + }); + } +} +exports.SecurityGroup = SecurityGroup; diff --git a/src/fargate.ts b/src/fargate.ts index 44ee479..71c83d8 100644 --- a/src/fargate.ts +++ b/src/fargate.ts @@ -12,6 +12,7 @@ import * as targets from 'aws-cdk-lib/aws-route53-targets' import { StackConfig } from './configuration' import { ARecord } from './route53' import * as elb from 'aws-cdk-lib/aws-elasticloadbalancingv2' +import { SecurityGroup } from './security-group' // Default docker image to use const DEFAULT_IMAGE = 'nginxdemos/hello:latest' @@ -50,6 +51,7 @@ interface FargateServiceProps extends cdk.NestedStackProps { repository?: ecr.IRepository taskConfiguration: TaskConfiguration image?: ecs.ContainerImage + securityGroup?: ec2.ISecurityGroup } /** @@ -77,6 +79,7 @@ export class FargateService extends cdk.NestedStack { zone, repository, taskConfiguration, + securityGroup: defaultSecurityGroup, } = props // Compile secrets into list of mapped ecs.Secrets @@ -104,10 +107,21 @@ export class FargateService extends cdk.NestedStack { ecs.ContainerImage.fromEcrRepository(repository, imageVersion)) || ecs.ContainerImage.fromRegistry(DEFAULT_IMAGE) + // Scaffold default security group if not provided + let securityGroup = defaultSecurityGroup + if (!securityGroup) { + const groupStack = new SecurityGroup( + this, + stack.getResourceID('SecurityGroup'), + { vpc: cluster.vpc }, + ) + securityGroup = groupStack.securityGroup + } + const desiredCount = taskConfiguration?.desiredCount || 1 this.service = new ecs_patterns.ApplicationLoadBalancedFargateService( this, - stack.getResourceID('AdminService'), + stack.getResourceID('FargateService'), { assignPublicIp: true, cluster: cluster, @@ -127,6 +141,7 @@ export class FargateService extends cdk.NestedStack { subnetType: ec2.SubnetType.PUBLIC, onePerAz: true, }, + securityGroups: [securityGroup], }, ) diff --git a/src/index.ts b/src/index.ts index 9ca5447..85d0328 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ export * from './helper' export * from './ipv6vpc' export * from './rds' export * from './route53' +export * from './security-group' export * from './s3' export * from './vpc' export * from './website-distribution' diff --git a/src/rds.ts b/src/rds.ts index 1beb204..ceb6d91 100644 --- a/src/rds.ts +++ b/src/rds.ts @@ -128,6 +128,8 @@ export class PostgresInstanceWithBastion extends PostgresInstance { stack.getResourceID('BastionSecurityGroup'), { vpc, + allowAllOutbound: true, + allowAllIpv6Outbound: true, }, ) diff --git a/src/security-group.ts b/src/security-group.ts new file mode 100644 index 0000000..46c624b --- /dev/null +++ b/src/security-group.ts @@ -0,0 +1,43 @@ +import { Construct } from 'constructs' +import * as cdk from 'aws-cdk-lib' +import * as ec2 from 'aws-cdk-lib/aws-ec2' + +export interface SecurityGroupProps extends cdk.NestedStackProps { + vpc: ec2.IVpc + incomingPorts?: number[] +} + +/** + * Default security group suitable for a basic ECS service + */ +export class SecurityGroup extends cdk.NestedStack { + public readonly securityGroup: ec2.ISecurityGroup + + constructor(scope: Construct, id: string, props: SecurityGroupProps) { + super(scope, id, props) + const { vpc, incomingPorts } = props + + this.securityGroup = new ec2.SecurityGroup(this, `${id}Group`, { + vpc, + description: 'Allows HTTP/HTTPS ingress and all egress traffic', + allowAllOutbound: true, + allowAllIpv6Outbound: true, + }) + + const portsToOpen = incomingPorts || [80, 443] + + // Open all selected ports + portsToOpen.forEach((port) => { + this.securityGroup.addIngressRule( + ec2.Peer.anyIpv4(), + ec2.Port.tcp(port), + `Allow ${port} from anywhere (IPv4)`, + ) + this.securityGroup.addIngressRule( + ec2.Peer.anyIpv6(), + ec2.Port.tcp(port), + `Allow ${port} from anywhere (IPv6)`, + ) + }) + } +}