Skip to content
This repository was archived by the owner on Nov 27, 2023. It is now read-only.

Commit b295152

Browse files
committed
ECS: Creating special security groups for ingress, instead of adding the ingress rule to other security groups
Solves #1783 Previously, the ECS stack included an ingress rule to allow LB to reach the tasks. However, it added this ingress rule toe very Docker network security group, meaning other tasks on the same Docker network, possibly sensitive, were accessible externally. We now create a new security group for port assignments for every service that has ports, and attach that security group only to that service. This prevents other tasks in the same Docker networks are not accessible externally.
1 parent bd636e0 commit b295152

4 files changed

+152
-29
lines changed

ecs/awsResources.go

+28-3
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ func (r *awsResources) serviceSecurityGroups(service types.ServiceConfig) []stri
5151
for net := range service.Networks {
5252
groups = append(groups, r.securityGroups[net])
5353
}
54+
if len(service.Ports) > 0 {
55+
groups = append(groups, r.securityGroups[serviceIngressSecGroupName(service.Name, false)])
56+
}
5457
return groups
5558
}
5659

@@ -330,6 +333,7 @@ func (b *ecsAPIService) ensureCluster(r *awsResources, project *types.Project, t
330333

331334
func (b *ecsAPIService) ensureNetworks(r *awsResources, project *types.Project, template *cloudformation.Template) {
332335
if r.securityGroups == nil {
336+
// TODO NITZ change the size hint?
333337
r.securityGroups = make(map[string]string, len(project.Networks))
334338
}
335339
for name, net := range project.Networks {
@@ -353,6 +357,27 @@ func (b *ecsAPIService) ensureNetworks(r *awsResources, project *types.Project,
353357

354358
r.securityGroups[name] = cloudformation.Ref(securityGroup)
355359
}
360+
361+
for _, service := range project.Services {
362+
if len(service.Ports) == 0 {
363+
continue
364+
}
365+
lbSecurityGroup := serviceIngressSecGroupName(service.Name, true)
366+
template.Resources[lbSecurityGroup] = &ec2.SecurityGroup{
367+
GroupDescription: fmt.Sprintf("%s Security Group for service %s ingress, LB side", project.Name, service.Name),
368+
VpcId: r.vpc,
369+
Tags: serviceTags(project, service),
370+
}
371+
r.securityGroups[lbSecurityGroup] = cloudformation.Ref(lbSecurityGroup)
372+
373+
serviceSecurityGroup := serviceIngressSecGroupName(service.Name, false)
374+
template.Resources[serviceSecurityGroup] = &ec2.SecurityGroup{
375+
GroupDescription: fmt.Sprintf("%s Security Group for service %s ingress, Service side", project.Name, service.Name),
376+
VpcId: r.vpc,
377+
Tags: serviceTags(project, service),
378+
}
379+
r.securityGroups[serviceSecurityGroup] = cloudformation.Ref(serviceSecurityGroup)
380+
}
356381
}
357382

358383
func (b *ecsAPIService) ensureVolumes(r *awsResources, project *types.Project, template *cloudformation.Template) error {
@@ -465,9 +490,9 @@ func (b *ecsAPIService) ensureLoadBalancer(r *awsResources, project *types.Proje
465490

466491
func (r *awsResources) getLoadBalancerSecurityGroups(project *types.Project) []string {
467492
securityGroups := []string{}
468-
for name, network := range project.Networks {
469-
if !network.Internal {
470-
securityGroups = append(securityGroups, r.securityGroups[name])
493+
for _, service := range project.Services {
494+
if len(service.Ports) > 0 {
495+
securityGroups = append(securityGroups, r.securityGroups[serviceIngressSecGroupName(service.Name, true)])
471496
}
472497
}
473498
return securityGroups

ecs/cloudformation.go

+34-4
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,15 @@ func (b *ecsAPIService) createService(project *types.Project, service types.Serv
197197
dependsOn []string
198198
serviceLB []ecs.Service_LoadBalancer
199199
)
200-
for _, port := range service.Ports {
201-
for net := range service.Networks {
202-
b.createIngress(service, net, port, template, resources)
203-
}
204200

201+
for _, port := range service.Ports {
202+
lbSecurityGroupName := serviceIngressSecGroupName(service.Name, true)
203+
serviceSecurityGroupName := serviceIngressSecGroupName(service.Name, false)
204+
// outside to ingress
205+
b.createIngress(service, lbSecurityGroupName, port, template, resources)
206+
// ingress to service
207+
b.createCrossGroupIngress(service, lbSecurityGroupName, serviceSecurityGroupName, port, template, resources)
208+
// TODO attach new secgroup to this service
205209
protocol := strings.ToUpper(port.Protocol)
206210
if resources.loadBalancerType == elbv2.LoadBalancerTypeEnumApplication {
207211
// we don't set Https as a certificate must be specified for HTTPS listeners
@@ -293,6 +297,22 @@ func (b *ecsAPIService) createIngress(service types.ServiceConfig, net string, p
293297
}
294298
}
295299

300+
func (b *ecsAPIService) createCrossGroupIngress(service types.ServiceConfig, source string, dest string, port types.ServicePortConfig, template *cloudformation.Template, resources awsResources) {
301+
protocol := strings.ToUpper(port.Protocol)
302+
if protocol == "" {
303+
protocol = allProtocols
304+
}
305+
ingress := fmt.Sprintf("%s%d%sIngress", normalizeResourceName(source), port.Target, normalizeResourceName(dest))
306+
template.Resources[ingress] = &ec2.SecurityGroupIngress{
307+
SourceSecurityGroupId: resources.securityGroups[source],
308+
Description: fmt.Sprintf("LB connectivity on %d", port.Target),
309+
GroupId: resources.securityGroups[dest],
310+
FromPort: int(port.Target),
311+
IpProtocol: protocol,
312+
ToPort: int(port.Target),
313+
}
314+
}
315+
296316
func (b *ecsAPIService) createSecret(project *types.Project, name string, s types.SecretConfig, template *cloudformation.Template) error {
297317
if s.External.External {
298318
return nil
@@ -534,6 +554,16 @@ func networkResourceName(network string) string {
534554
return fmt.Sprintf("%sNetwork", normalizeResourceName(network))
535555
}
536556

557+
func serviceIngressSecGroupName(service string, isLBSide bool) string {
558+
var header string
559+
if isLBSide {
560+
header = "LB"
561+
} else {
562+
header = "Service"
563+
}
564+
return fmt.Sprintf("%s%sIngressSecurityGroup", normalizeResourceName(service), header)
565+
}
566+
537567
func serviceResourceName(service string) string {
538568
return fmt.Sprintf("%sService", normalizeResourceName(service))
539569
}

ecs/testdata/simple-cloudformation-conversion.golden

+45-11
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,6 @@ Resources:
1313
- Key: com.docker.compose.project
1414
Value: TestSimpleConvert
1515
Type: AWS::ECS::Cluster
16-
Default80Ingress:
17-
Properties:
18-
CidrIp: 0.0.0.0/0
19-
Description: simple:80/tcp on default network
20-
FromPort: 80
21-
GroupId:
22-
Ref: DefaultNetwork
23-
IpProtocol: TCP
24-
ToPort: 80
25-
Type: AWS::EC2::SecurityGroupIngress
2616
DefaultNetwork:
2717
Properties:
2818
GroupDescription: TestSimpleConvert Security Group for default network
@@ -46,7 +36,7 @@ Resources:
4636
Properties:
4737
Scheme: internet-facing
4838
SecurityGroups:
49-
- Ref: DefaultNetwork
39+
- Ref: SimpleLBIngressSecurityGroup
5040
Subnets:
5141
- subnet1
5242
- subnet2
@@ -59,6 +49,38 @@ Resources:
5949
Properties:
6050
LogGroupName: /docker-compose/TestSimpleConvert
6151
Type: AWS::Logs::LogGroup
52+
SimpleLBIngressSecurityGroup:
53+
Properties:
54+
GroupDescription: TestSimpleConvert Security Group for service simple ingress,
55+
LB side
56+
Tags:
57+
- Key: com.docker.compose.project
58+
Value: TestSimpleConvert
59+
- Key: com.docker.compose.service
60+
Value: simple
61+
VpcId: vpc-123
62+
Type: AWS::EC2::SecurityGroup
63+
SimpleLBIngressSecurityGroup80Ingress:
64+
Properties:
65+
CidrIp: 0.0.0.0/0
66+
Description: simple:80/tcp on SimpleLBIngressSecurityGroup network
67+
FromPort: 80
68+
GroupId:
69+
Ref: SimpleLBIngressSecurityGroup
70+
IpProtocol: TCP
71+
ToPort: 80
72+
Type: AWS::EC2::SecurityGroupIngress
73+
SimpleLBIngressSecurityGroup80SimpleServiceIngressSecurityGroupIngress:
74+
Properties:
75+
Description: LB connectivity on 80
76+
FromPort: 80
77+
GroupId:
78+
Ref: SimpleServiceIngressSecurityGroup
79+
IpProtocol: TCP
80+
SourceSecurityGroupId:
81+
Ref: SimpleLBIngressSecurityGroup
82+
ToPort: 80
83+
Type: AWS::EC2::SecurityGroupIngress
6284
SimpleService:
6385
DependsOn:
6486
- SimpleTCP80Listener
@@ -84,6 +106,7 @@ Resources:
84106
AssignPublicIp: ENABLED
85107
SecurityGroups:
86108
- Ref: DefaultNetwork
109+
- Ref: SimpleServiceIngressSecurityGroup
87110
Subnets:
88111
- subnet1
89112
- subnet2
@@ -117,6 +140,17 @@ Resources:
117140
NamespaceId:
118141
Ref: CloudMap
119142
Type: AWS::ServiceDiscovery::Service
143+
SimpleServiceIngressSecurityGroup:
144+
Properties:
145+
GroupDescription: TestSimpleConvert Security Group for service simple ingress,
146+
Service side
147+
Tags:
148+
- Key: com.docker.compose.project
149+
Value: TestSimpleConvert
150+
- Key: com.docker.compose.service
151+
Value: simple
152+
VpcId: vpc-123
153+
Type: AWS::EC2::SecurityGroup
120154
SimpleTCP80Listener:
121155
Properties:
122156
DefaultActions:

ecs/testdata/slightly-complex-cloudformation-conversion.golden

+45-11
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,6 @@ Resources:
1313
- Key: com.docker.compose.project
1414
Value: TestSlightlyComplexConvert
1515
Type: AWS::ECS::Cluster
16-
Default80Ingress:
17-
Properties:
18-
CidrIp: 0.0.0.0/0
19-
Description: entrance:80/tcp on default network
20-
FromPort: 80
21-
GroupId:
22-
Ref: DefaultNetwork
23-
IpProtocol: TCP
24-
ToPort: 80
25-
Type: AWS::EC2::SecurityGroupIngress
2616
DefaultNetwork:
2717
Properties:
2818
GroupDescription: TestSlightlyComplexConvert Security Group for default network
@@ -42,6 +32,38 @@ Resources:
4232
SourceSecurityGroupId:
4333
Ref: DefaultNetwork
4434
Type: AWS::EC2::SecurityGroupIngress
35+
EntranceLBIngressSecurityGroup:
36+
Properties:
37+
GroupDescription: TestSlightlyComplexConvert Security Group for service entrance
38+
ingress, LB side
39+
Tags:
40+
- Key: com.docker.compose.project
41+
Value: TestSlightlyComplexConvert
42+
- Key: com.docker.compose.service
43+
Value: entrance
44+
VpcId: vpc-123
45+
Type: AWS::EC2::SecurityGroup
46+
EntranceLBIngressSecurityGroup80EntranceServiceIngressSecurityGroupIngress:
47+
Properties:
48+
Description: LB connectivity on 80
49+
FromPort: 80
50+
GroupId:
51+
Ref: EntranceServiceIngressSecurityGroup
52+
IpProtocol: TCP
53+
SourceSecurityGroupId:
54+
Ref: EntranceLBIngressSecurityGroup
55+
ToPort: 80
56+
Type: AWS::EC2::SecurityGroupIngress
57+
EntranceLBIngressSecurityGroup80Ingress:
58+
Properties:
59+
CidrIp: 0.0.0.0/0
60+
Description: entrance:80/tcp on EntranceLBIngressSecurityGroup network
61+
FromPort: 80
62+
GroupId:
63+
Ref: EntranceLBIngressSecurityGroup
64+
IpProtocol: TCP
65+
ToPort: 80
66+
Type: AWS::EC2::SecurityGroupIngress
4567
EntranceService:
4668
DependsOn:
4769
- EntranceTCP80Listener
@@ -67,6 +89,7 @@ Resources:
6789
AssignPublicIp: ENABLED
6890
SecurityGroups:
6991
- Ref: DefaultNetwork
92+
- Ref: EntranceServiceIngressSecurityGroup
7093
Subnets:
7194
- subnet1
7295
- subnet2
@@ -100,6 +123,17 @@ Resources:
100123
NamespaceId:
101124
Ref: CloudMap
102125
Type: AWS::ServiceDiscovery::Service
126+
EntranceServiceIngressSecurityGroup:
127+
Properties:
128+
GroupDescription: TestSlightlyComplexConvert Security Group for service entrance
129+
ingress, Service side
130+
Tags:
131+
- Key: com.docker.compose.project
132+
Value: TestSlightlyComplexConvert
133+
- Key: com.docker.compose.service
134+
Value: entrance
135+
VpcId: vpc-123
136+
Type: AWS::EC2::SecurityGroup
103137
EntranceTCP80Listener:
104138
Properties:
105139
DefaultActions:
@@ -192,7 +226,7 @@ Resources:
192226
Properties:
193227
Scheme: internet-facing
194228
SecurityGroups:
195-
- Ref: DefaultNetwork
229+
- Ref: EntranceLBIngressSecurityGroup
196230
Subnets:
197231
- subnet1
198232
- subnet2

0 commit comments

Comments
 (0)