Skip to content

Commit c2c893d

Browse files
authored
Services security groups in own stack (#750)
1 parent f1facf1 commit c2c893d

File tree

7 files changed

+288
-278
lines changed

7 files changed

+288
-278
lines changed

ecs_composex/ecs/ecs_family/__init__.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -304,9 +304,10 @@ def generate_outputs(self):
304304
self.outputs.append(
305305
CfnOutput(
306306
f"{self.logical_name}GroupId",
307-
Value=GetAtt(self.service_networking.security_group, "GroupId"),
307+
Value=Ref(self.service_networking.security_group.parameter.title),
308308
)
309309
)
310+
if ecs_params.SERVICE_SUBNETS.title in self.stack.stack_template.parameters:
310311
self.outputs.append(
311312
CfnOutput(
312313
ecs_params.SERVICE_SUBNETS.title,
@@ -423,7 +424,6 @@ def init_network_settings(
423424
"""
424425
Once we have figured out the compute settings (EXTERNAL vs other)
425426
"""
426-
from ecs_composex.ecs.service_networking.helpers import add_security_group
427427

428428
self.service_networking = ServiceNetworking(self, families_sg_stack)
429429
self.finalize_services_networking_settings(settings)
@@ -434,15 +434,14 @@ def init_network_settings(
434434
self.stack.set_vpc_params_from_vpc_lookup(vpc_stack, settings)
435435
else:
436436
self.stack.set_vpc_parameters_from_vpc_stack(vpc_stack, settings)
437-
add_security_group(self)
438437
self.service_networking.ingress.set_aws_sources_ingress(
439438
settings,
440439
self.logical_name,
441-
GetAtt(self.service_networking.security_group, "GroupId"),
440+
Ref(self.service_networking.security_group.parameter.title),
442441
)
443442
self.service_networking.ingress.set_ext_sources_ingress(
444443
self.logical_name,
445-
GetAtt(self.service_networking.security_group, "GroupId"),
444+
Ref(self.service_networking.security_group.parameter.title),
446445
)
447446
self.service_networking.ingress.associate_aws_ingress_rules(self.template)
448447
self.service_networking.ingress.associate_ext_ingress_rules(self.template)

ecs_composex/ecs/service_networking/__init__.py

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
ServiceSecurityGroup,
1818
)
1919
from ecs_composex.common.settings import ComposeXSettings
20+
from troposphere.ecs import ServiceConnectConfiguration
2021

2122
from itertools import chain
2223

@@ -76,11 +77,10 @@ def __init__(self, family: ComposeFamily, families_sg_stack: EcsIngressStack):
7677
self.merge_services_ports()
7778
self.merge_networks()
7879
self.definition = merge_family_services_networking(family)
79-
self._security_group = None
80-
self.inter_services_sg: ServiceSecurityGroup = (
81-
families_sg_stack.services_mappings[family.name]
82-
)
83-
self.extra_security_groups = [self.inter_services_sg.parameter]
80+
self.security_group: ServiceSecurityGroup = families_sg_stack.services_mappings[
81+
family.name
82+
]
83+
self.extra_security_groups = [self.security_group.parameter]
8484
self._subnets = Ref(APP_SUBNETS)
8585
self.cloudmap_config = (
8686
merge_cloudmap_settings(family, self.ports) if self.ports else {}
@@ -139,7 +139,7 @@ def eip_assign(self):
139139

140140
@property
141141
def security_groups(self) -> list:
142-
groups = [Ref(self.security_group)]
142+
groups = [Ref(self.security_group.parameter.title)]
143143
for extra_group in self.extra_security_groups:
144144
if (
145145
isinstance(extra_group, SecurityGroup)
@@ -153,23 +153,6 @@ def security_groups(self) -> list:
153153
groups.append(extra_group)
154154
return groups
155155

156-
@property
157-
def security_group(self):
158-
return self._security_group
159-
160-
@security_group.setter
161-
def security_group(self, value):
162-
if isinstance(value, SecurityGroup):
163-
self._security_group = value
164-
else:
165-
raise TypeError(
166-
"Service security group must be",
167-
SecurityGroup,
168-
"Got",
169-
value,
170-
type(value),
171-
)
172-
173156
@property
174157
def network_mode(self):
175158
"""
@@ -270,11 +253,11 @@ def add_self_ingress(self) -> None:
270253
FromPort=target_port,
271254
ToPort=target_port,
272255
IpProtocol=port["protocol"],
273-
GroupId=GetAtt(
274-
self.family.service_networking.security_group, "GroupId"
256+
GroupId=Ref(
257+
self.family.service_networking.security_group.parameter
275258
),
276-
SourceSecurityGroupId=GetAtt(
277-
self.family.service_networking.security_group, "GroupId"
259+
SourceSecurityGroupId=Ref(
260+
self.family.service_networking.security_group.parameter
278261
),
279262
SourceSecurityGroupOwnerId=Ref(AWS_ACCOUNT_ID),
280263
Description=Sub(
@@ -299,9 +282,7 @@ def add_lb_ingress(self, lb_name, lb_sg_ref) -> None:
299282
"FromPort": port["target"],
300283
"ToPort": port["target"],
301284
"IpProtocol": port["protocol"],
302-
"GroupId": GetAtt(
303-
self.family.service_networking.security_group, "GroupId"
304-
),
285+
"GroupId": Ref(self.security_group.parameter.title),
305286
"SourceSecurityGroupOwnerId": Ref(AWS_ACCOUNT_ID),
306287
"Description": Sub(
307288
f"From ELB {lb_name} to ${{{SERVICE_NAME.title}}} on port {port['target']}"

ecs_composex/ecs/service_networking/helpers.py

Lines changed: 15 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,23 @@
11
# SPDX-License-Identifier: MPL-2.0
22
# Copyright 2020-2022 John Mille <[email protected]>
33

4-
from troposphere import Ref, StackName, Sub, Tags
5-
from troposphere.ec2 import SecurityGroup
4+
from __future__ import annotations
65

7-
from ecs_composex.common.cfn_conditions import define_stack_name
8-
from ecs_composex.common.logging import LOG
9-
from ecs_composex.common.troposphere_tools import add_resource
10-
from ecs_composex.ecs.ecs_params import SERVICE_NAME, SG_T
11-
from ecs_composex.vpc.vpc_params import VPC_ID
6+
from typing import TYPE_CHECKING
127

8+
if TYPE_CHECKING:
9+
from ecs_composex.ecs.ecs_family import ComposeFamily
10+
from ecs_composex.common.settings import ComposeXSettings
1311

14-
def add_security_group(family) -> None:
15-
"""
16-
Creates a new EC2 SecurityGroup and assigns to ecs_service.network_settings
17-
Adds the security group to the family template resources.
12+
from troposphere import Ref
1813

19-
:param ecs_composex.ecs.ecs_family.ComposeFamily family:
20-
"""
21-
family.service_networking.security_group = SecurityGroup(
22-
SG_T,
23-
GroupDescription=Sub(
24-
f"SG for ${{{SERVICE_NAME.title}}} - ${{STACK_NAME}}",
25-
STACK_NAME=define_stack_name(),
26-
),
27-
Tags=Tags(
28-
{
29-
"Name": Sub(
30-
f"${{{SERVICE_NAME.title}}}-${{STACK_NAME}}",
31-
STACK_NAME=define_stack_name(),
32-
),
33-
"StackName": StackName,
34-
"MicroserviceName": Ref(SERVICE_NAME),
35-
}
36-
),
37-
VpcId=Ref(VPC_ID),
38-
)
39-
add_resource(family.template, family.service_networking.security_group)
14+
from ecs_composex.common.logging import LOG
4015

4116

42-
def update_family_subnets(family, settings) -> None:
17+
def update_family_subnets(family: ComposeFamily, settings: ComposeXSettings) -> None:
4318
"""
44-
Method to update the stack parameters
45-
46-
:param ecs_composex.ecs.ecs_family.ComposeFamily family:
47-
:param ecs_composex.common.settings.ComposeXSettings settings:
19+
Update the stack parameters of the family stack AppSubnets Parameter value to the one matching with
20+
networks.x-vpc and networks.[]
4821
"""
4922
network_names = list(family.service_networking.networks.keys())
5023
for network in settings.networks:
@@ -63,7 +36,11 @@ def update_family_subnets(family, settings) -> None:
6336
)
6437

6538

66-
def set_family_hostname(family):
39+
def set_family_hostname(family: ComposeFamily):
40+
"""
41+
Sets the hostname to use for the Family in Cloudmap.
42+
If it has been set on more than one service container, it uses the first one.
43+
"""
6744
svcs_hostnames = any(svc.family_hostname for svc in family.services)
6845
if not svcs_hostnames or not family.family_hostname:
6946
LOG.debug(

ecs_composex/ecs/service_networking/ingress_helpers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,11 @@ def add_service_to_service_ingress_rules(
216216
SecurityGroupIngress(
217217
ingress_title,
218218
SourceSecurityGroupId=GetAtt(
219-
_service.family.service_networking.inter_services_sg.cfn_resource,
219+
_service.family.service_networking.security_group.cfn_resource,
220220
"GroupId",
221221
),
222222
GroupId=GetAtt(
223-
dst_family.service_networking.inter_services_sg.cfn_resource,
223+
dst_family.service_networking.security_group.cfn_resource,
224224
"GroupId",
225225
),
226226
**common_args,

ecs_composex/ingress_settings.py

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@
55
Module to help with defining the network settings for the ECS Service based on the family services definitions.
66
"""
77

8+
from __future__ import annotations
9+
10+
from typing import TYPE_CHECKING
11+
12+
if TYPE_CHECKING:
13+
from troposphere import AWSHelperFn
14+
from ecs_composex.common.settings import ComposeXSettings
15+
816
import re
917
from copy import deepcopy
1018
from ipaddress import IPv4Interface
@@ -153,14 +161,8 @@ def set_service_ports(ports):
153161
return service_ports
154162

155163

156-
def lookup_security_group(settings, lookup):
157-
"""
158-
Function to fetch the security group ID based on lookup details
159-
160-
:param ecs_composex.common.settings.ComposeXSettings settings:
161-
:param lookup:
162-
:return:
163-
"""
164+
def lookup_security_group(settings: ComposeXSettings, lookup: dict | list) -> str:
165+
"""Function to fetch the security group ID based on lookup details"""
164166
sg_re = re.compile(
165167
r"^arn:aws(?:-[a-z]+)?:ec2:[a-z0-9-]+:\d{12}:security-group/([\S]+)$"
166168
)
@@ -239,14 +241,48 @@ def __init__(self, definition, ports):
239241
def __repr__(self):
240242
return dumps(self.definition, indent=2)
241243

242-
def set_aws_sources_ingress(self, settings, destination_title, sg_ref) -> None:
244+
def handle_security_group_source(
245+
self,
246+
source,
247+
common_args: dict,
248+
destination_title: str,
249+
target_port: int,
250+
settings,
251+
) -> None:
243252
"""
244-
Method to define AWS Sources ingresses
245-
246-
:param settings:
247-
:param destination_title:
248-
:param sg_ref:
253+
Method to handle SecurityGroup sources
254+
It updates the list of AWS sources ingress rules that will later be added to the stack template of the family
249255
"""
256+
if keyisset("Id", source):
257+
sg_id = source["Id"]
258+
elif keyisset("Lookup", source):
259+
sg_id = lookup_security_group(settings, source["Lookup"])
260+
else:
261+
raise KeyError(
262+
"Information missing to identify the SecurityGroup. Requires either Id or Lookup"
263+
)
264+
common_args.update(
265+
{
266+
"Description": Sub(
267+
f"From {sg_id} to {destination_title} on port {target_port}"
268+
)
269+
}
270+
)
271+
self.aws_ingress_rules.append(
272+
SecurityGroupIngress(
273+
f"From{NONALPHANUM.sub('', sg_id)}ToServiceOn{target_port}",
274+
SourceSecurityGroupId=sg_id,
275+
SourceSecurityGroupOwnerId=set_else_none(
276+
"AccountOwner", source, Ref(AWS_ACCOUNT_ID)
277+
),
278+
**common_args,
279+
)
280+
)
281+
282+
def set_aws_sources_ingress(
283+
self, settings: ComposeXSettings, destination_title: str, sg_ref: AWSHelperFn
284+
) -> None:
285+
"""Method to define AWS Sources ingresses"""
250286
for source in self.aws_sources:
251287
for port in self.ports:
252288
if (
@@ -269,30 +305,8 @@ def set_aws_sources_ingress(self, settings, destination_title, sg_ref) -> None:
269305
"GroupId": sg_ref,
270306
}
271307
if source["Type"] == "SecurityGroup":
272-
if keyisset("Id", source):
273-
sg_id = source["Id"]
274-
elif keyisset("Lookup", source):
275-
sg_id = lookup_security_group(settings, source["Lookup"])
276-
else:
277-
raise KeyError(
278-
"Information missing to identify the SecurityGroup. Requires either Id or Lookup"
279-
)
280-
common_args.update(
281-
{
282-
"Description": Sub(
283-
f"From {sg_id} to {destination_title} on port {target_port}"
284-
)
285-
}
286-
)
287-
self.aws_ingress_rules.append(
288-
SecurityGroupIngress(
289-
f"From{NONALPHANUM.sub('', sg_id)}ToServiceOn{target_port}",
290-
SourceSecurityGroupId=sg_id,
291-
SourceSecurityGroupOwnerId=set_else_none(
292-
"AccountOwner", source, Ref(AWS_ACCOUNT_ID)
293-
),
294-
**common_args,
295-
)
308+
self.handle_security_group_source(
309+
source, common_args, destination_title, target_port, settings
296310
)
297311
elif source["Type"] == "PrefixList":
298312
self.aws_ingress_rules.append(
@@ -304,7 +318,7 @@ def set_aws_sources_ingress(self, settings, destination_title, sg_ref) -> None:
304318
)
305319

306320
def create_ext_sources_ingress_rule(
307-
self, destination_title, allowed_source, security_group, **props
321+
self, destination_title, allowed_source, security_group: AWSHelperFn, **props
308322
) -> None:
309323
"""
310324
Creates the Security Ingress rule for a CIDR based rule

ecs_composex/rds_resources_settings.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
DatabaseXResource,
1818
)
1919
from ecs_composex.common.stacks import ComposeXStack
20+
from ecs_composex.ecs.ecs_stack import ServiceStack
2021

2122
from botocore.exceptions import ClientError
2223
from compose_x_common.aws import get_account_id
@@ -380,25 +381,20 @@ def add_secret_to_container(db, secret_import, service, target):
380381
extend_container_secrets(service.container_definition, db_secret)
381382

382383

383-
def add_security_group_ingress(service_stack: ComposeXStack, db_name: str, sg_id, port):
384+
def add_security_group_ingress(family: ComposeFamily, db_name: str, sg_id, port):
384385
"""
385-
Function to add a SecurityGroupIngress rule into the ECS Service template
386-
387-
:param ecs_composex.ecs.ServicesStack service_stack: The root stack for the services
388-
:param str db_name: the name of the database to use for imports
389-
:param sg_id: The security group Id to use for ingress. DB Security group, not service's
390-
:param port: The port for Ingress to the DB.
386+
Add a SecurityGroupIngress rule into the ECS Service template, allowing the Service access to the DB.
391387
"""
392388
add_resource(
393-
service_stack.stack_template,
389+
family.stack.stack_template,
394390
SecurityGroupIngress(
395-
f"AllowFrom{service_stack.title}to{db_name}",
391+
f"AllowFrom{family.stack.title}to{db_name}",
396392
GroupId=sg_id,
397393
FromPort=port,
398394
ToPort=port,
399-
Description=Sub(f"Allow FROM {service_stack.title} TO {db_name}"),
400-
SourceSecurityGroupId=GetAtt(
401-
service_stack.stack_template.resources[SG_T], "GroupId"
395+
Description=Sub(f"Allow FROM {family.stack.title} TO {db_name}"),
396+
SourceSecurityGroupId=Ref(
397+
family.service_networking.security_group.parameter.title
402398
),
403399
SourceSecurityGroupOwnerId=Ref("AWS::AccountId"),
404400
IpProtocol="6",
@@ -553,7 +549,7 @@ def handle_import_dbs_to_services(
553549
use_task_role=grant_task_role_access,
554550
)
555551
add_security_group_ingress(
556-
target[0].stack,
552+
target[0],
557553
db.logical_name,
558554
sg_id=db.attributes_outputs[db.security_group_param]["ImportValue"],
559555
port=db.attributes_outputs[db.port_param]["ImportValue"],
@@ -604,7 +600,7 @@ def handle_new_tcp_resource(
604600
)
605601

606602
add_security_group_ingress(
607-
target[0].stack,
603+
target[0],
608604
resource.logical_name,
609605
sg_id=Ref(sg_id["ImportParameter"]),
610606
port=Ref(port_id["ImportParameter"]),

0 commit comments

Comments
 (0)