From 4414619332e2609ec524d0d509c8ed001a5eff45 Mon Sep 17 00:00:00 2001 From: droguljic <1875821+droguljic@users.noreply.github.com> Date: Wed, 25 Feb 2026 19:00:52 +0100 Subject: [PATCH] fix: utilize provided security group in ECS service Add the provided security group to the list of security groups to aid in configuring network settings and to prevent from creating default security group. Alters signature of the `addSecurityGroup` method to accept `pulumi.Input` and reduce visibility to private as it is instance method that is only used during construction. --- src/components/ecs-service/index.ts | 12 ++++- tests/ecs-service/index.test.ts | 2 + tests/ecs-service/infrastructure/index.ts | 38 +++++++++++++++ tests/ecs-service/security-group.test.ts | 57 +++++++++++++++++++++++ 4 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 tests/ecs-service/security-group.test.ts diff --git a/src/components/ecs-service/index.ts b/src/components/ecs-service/index.ts index fa25ccc7..9d4975c9 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 59200d09..4fd715c2 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 243bf5b0..478c4323 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 00000000..b42db4c2 --- /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', + ); + }); +}