diff --git a/src/components/ecs-service/index.ts b/src/components/ecs-service/index.ts index fa25ccc..9d4975c 100644 --- a/src/components/ecs-service/index.ts +++ b/src/components/ecs-service/index.ts @@ -440,7 +440,9 @@ export class EcsService extends pulumi.ComponentResource { ); } - public addSecurityGroup(securityGroup: aws.ec2.SecurityGroup): void { + private addSecurityGroup( + securityGroup: pulumi.Input, + ): void { this.securityGroups.push(pulumi.output(securityGroup)); } @@ -473,7 +475,13 @@ export class EcsService extends pulumi.ComponentResource { } private createEcsService(ecsServiceArgs: EcsService.Args) { - if (!this.securityGroups.length) this.createDefaultSecurityGroup(); + if (ecsServiceArgs.securityGroup) { + this.addSecurityGroup(ecsServiceArgs.securityGroup); + } + + if (!this.securityGroups.length) { + this.createDefaultSecurityGroup(); + } const networkConfiguration = { assignPublicIp: ecsServiceArgs.assignPublicIp, diff --git a/tests/ecs-service/index.test.ts b/tests/ecs-service/index.test.ts index 59200d0..4fd715c 100644 --- a/tests/ecs-service/index.test.ts +++ b/tests/ecs-service/index.test.ts @@ -16,6 +16,7 @@ import { backOff } from 'exponential-backoff'; import * as automation from '../automation'; import { EcsTestContext } from './test-context'; import { testEcsServiceWithLb } from './load-balancer.test'; +import { testEcsServiceWithSg } from './security-group.test'; import { testEcsServiceWithStorage } from './persistent-storage.test'; import { testEcsServiceWithServiceDiscovery } from './service-discovery.test'; import { testEcsServiceWithAutoscaling } from './autoscaling.test'; @@ -278,4 +279,5 @@ describe('EcsService component deployment', () => { testEcsServiceWithServiceDiscovery(ctx)); describe('With persistent storage', () => testEcsServiceWithStorage(ctx)); describe('With load balancer', () => testEcsServiceWithLb(ctx)); + describe('With security group', () => testEcsServiceWithSg(ctx)); }); diff --git a/tests/ecs-service/infrastructure/index.ts b/tests/ecs-service/infrastructure/index.ts index 243bf5b..478c432 100644 --- a/tests/ecs-service/infrastructure/index.ts +++ b/tests/ecs-service/infrastructure/index.ts @@ -134,6 +134,42 @@ const ecsServiceWithLb = new studion.EcsService( const lbUrl = pulumi.interpolate`http://${lb.dnsName}`; +const ecsSecurityGroup = new aws.ec2.SecurityGroup( + `${appName}-ecs-sg`, + { + vpcId: vpc.vpc.vpcId, + ingress: [ + { + fromPort: 0, + toPort: 0, + protocol: '-1', + securityGroups: [vpc.vpc.vpc.defaultSecurityGroupId], + }, + ], + egress: [ + { + fromPort: 0, + toPort: 0, + protocol: '-1', + cidrBlocks: ['0.0.0.0/0'], + }, + ], + }, + { parent }, +); + +const ecsServiceWithSg = new studion.EcsService( + `${appName}-sg`, + { + cluster, + vpc: vpc.vpc, + containers: [sampleServiceContainer], + securityGroup: ecsSecurityGroup, + tags, + }, + { parent }, +); + const ecsWithDiscovery = new studion.EcsService( `${appName}-sd`, { @@ -231,6 +267,8 @@ export { minimalEcsService, ecsServiceWithLb, lbUrl, + ecsSecurityGroup, + ecsServiceWithSg, ecsWithDiscovery, ecsServiceWithAutoscaling, ecsServiceWithStorage, diff --git a/tests/ecs-service/security-group.test.ts b/tests/ecs-service/security-group.test.ts new file mode 100644 index 0000000..b42db4c --- /dev/null +++ b/tests/ecs-service/security-group.test.ts @@ -0,0 +1,57 @@ +import { it } from 'node:test'; +import * as assert from 'node:assert'; +import * as aws from '@pulumi/aws'; +import { EcsTestContext } from './test-context'; +import { Unwrap } from '@pulumi/pulumi'; + +export function testEcsServiceWithSg(ctx: EcsTestContext) { + it('should export security group when provided', () => { + const ecsService = ctx.outputs.ecsServiceWithSg.value; + const securityGroup = ctx.outputs.ecsSecurityGroup.value; + + assert.ok( + ecsService.securityGroups.some( + (sg: aws.ec2.SecurityGroup) => sg.id === securityGroup.id, + ), + 'ECS service should export provided security group', + ); + }); + + it('should use security group to configure network settings when provided', () => { + const ecsService = ctx.outputs.ecsServiceWithSg.value; + const securityGroup = ctx.outputs.ecsSecurityGroup.value; + const networkConfig = ecsService.service.networkConfiguration; + + assert.ok( + networkConfig.securityGroups.includes(securityGroup.id), + 'Network setting should include provided security group', + ); + }); + + it('should not create security group with default rules', () => { + const ecsService = ctx.outputs.ecsServiceWithSg.value; + const vpc = ctx.outputs.vpc.value; + + const isDefault = (sg: aws.ec2.SecurityGroup) => { + const ingress = sg.ingress as unknown as Unwrap; + const egress = sg.egress as unknown as Unwrap; + + return ( + sg.vpcId === vpc.vpc.vpcId && + ingress.length === 1 && + ingress[0].protocol === '-1' && + ingress[0].cidrBlocks?.length === 1 && + ingress[0].cidrBlocks.includes(vpc.vpc.vpc.cidrBlock) && + egress.length === 1 && + egress[0].protocol === '-1' && + egress[0].cidrBlocks?.length === 1 && + egress[0].cidrBlocks.includes('0.0.0.0/0') + ); + }; + + assert.ok( + !ecsService.securityGroups.some(isDefault), + 'Security group with default rules should not be created', + ); + }); +}