Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 56 additions & 16 deletions infra/pulumi/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,18 +220,53 @@ def main():
pulumi.export("gha_ecr_publish_role_arn", gha_ecr_publish_role.arn)

# =========================================================================
# Security Groups
# Security Groups (accounts-repo pattern)
# =========================================================================
# Pattern: separate load_balancers and containers sections
# For each service, matching entries in both. Workers with no LB set to null.
# Code dynamically wires source_security_group_id from LB SG to container ingress.
sg_configs = resources.get("tb:network:SecurityGroupWithRules", {})
security_groups = {}

for sg_name, sg_config in sg_configs.items():
lb_sg_configs = sg_configs.get("load_balancers", {})
container_sg_configs = sg_configs.get("containers", {})

# Build security groups for load balancers
lb_sgs = {}
for service, sg_config in lb_sg_configs.items():
if sg_config is None:
lb_sgs[service] = None
continue
if vpc_resource:
sg_config["vpc_id"] = vpc_resource.id
lb_sgs[service] = tb_pulumi.network.SecurityGroupWithRules(
name=f"{project.name_prefix}-sg-lb-{service}",
project=project,
opts=pulumi.ResourceOptions(depends_on=[vpc] if vpc_config else None),
**sg_config,
)

security_groups[sg_name] = tb_pulumi.network.SecurityGroupWithRules(
name=f"{project.name_prefix}-{sg_name}",
# Build security groups for containers
# Wire source_security_group_id from LB SG to container ingress rules
container_sgs = {}
for service, sg_config in container_sg_configs.items():
if service not in lb_sgs:
pulumi.log.warn(f"Container SG '{service}' has no matching load_balancers entry")
# Dynamically set source_security_group_id for ingress rules
if lb_sgs.get(service) is not None:
for rule in sg_config.get("rules", {}).get("ingress", []):
if "self" not in rule or not rule.get("self"):
# Set source SG to the matching LB SG
rule["source_security_group_id"] = lb_sgs[service].resources["sg"].id
if vpc_resource:
sg_config["vpc_id"] = vpc_resource.id
depends_on = []
if lb_sgs.get(service):
depends_on.append(lb_sgs[service].resources["sg"])
if vpc_config:
depends_on.append(vpc)
container_sgs[service] = tb_pulumi.network.SecurityGroupWithRules(
name=f"{project.name_prefix}-sg-cont-{service}",
project=project,
opts=pulumi.ResourceOptions(depends_on=depends_on) if depends_on else None,
**sg_config,
)

Expand All @@ -247,23 +282,28 @@ def main():
subnets = private_subnets if is_internal else public_subnets

if subnets:
# Determine which security groups to apply
if service_name == "web":
container_sgs = [security_groups.get("web-sg")]
elif service_name == "worker":
container_sgs = [security_groups.get("worker-sg")]
else:
container_sgs = [security_groups.get("web-sg")] # Default
# Get security groups for this service
lb_sg = lb_sgs.get(service_name)
container_sg = container_sgs.get(service_name)

# Extract SG IDs
lb_sg_ids = [lb_sg.resources["sg"].id] if lb_sg else []
container_sg_ids = [container_sg.resources["sg"].id] if container_sg else []

# Filter out None values
container_sg_ids = [sg.resources["sg"].id for sg in container_sgs if sg is not None]
# Build depends_on list
depends_on = [*subnets]
if container_sg:
depends_on.append(container_sg.resources["sg"])
if lb_sg:
depends_on.append(lb_sg.resources["sg"])

fargate_services[service_name] = tb_pulumi.fargate.FargateClusterWithLogging(
name=f"{project.name_prefix}-{service_name}",
project=project,
subnets=[s.id for s in subnets] if subnets else [],
container_security_groups=container_sg_ids,
load_balancer_security_groups=container_sg_ids if not is_internal else [],
load_balancer_security_groups=lb_sg_ids if not is_internal else [],
opts=pulumi.ResourceOptions(depends_on=depends_on),
**service_config,
)

Expand Down
115 changes: 86 additions & 29 deletions infra/pulumi/config.stage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -314,36 +314,93 @@ resources:
# =============================================================================
# Security Groups
# =============================================================================
# Pattern from thunderbird-accounts: separate load_balancers and containers sections
# For each Fargate service, create matching entries in both sections.
# Services without LB (e.g., workers) set load_balancers entry to null.
# Code dynamically wires source_security_group_id from LB SG to container ingress.
tb:network:SecurityGroupWithRules:
web-sg:
description: Security group for web Fargate tasks
rules:
ingress:
- description: HTTPS from ALB
from_port: 8000
to_port: 8000
protocol: tcp
cidr_blocks:
- 10.100.0.0/16
egress:
- description: Allow all outbound
from_port: 0
to_port: 65535
protocol: tcp
cidr_blocks:
- 0.0.0.0/0

worker-sg:
description: Security group for worker Fargate tasks
rules:
ingress: []
egress:
- description: Allow all outbound
from_port: 0
to_port: 65535
protocol: tcp
cidr_blocks:
- 0.0.0.0/0
load_balancers:
web:
rules:
ingress:
- description: HTTPS from internet
from_port: 443
to_port: 443
protocol: tcp
cidr_blocks:
- 0.0.0.0/0
- description: HTTP redirect (optional)
from_port: 80
to_port: 80
protocol: tcp
cidr_blocks:
- 0.0.0.0/0
egress:
- description: Allow outbound to containers
from_port: 0
to_port: 65535
protocol: tcp
cidr_blocks:
- 0.0.0.0/0
versioncheck:
rules:
ingress:
- description: HTTPS from internet
from_port: 443
to_port: 443
protocol: tcp
cidr_blocks:
- 0.0.0.0/0
egress:
- description: Allow outbound to containers
from_port: 0
to_port: 65535
protocol: tcp
cidr_blocks:
- 0.0.0.0/0
worker: null # Workers don't have ALB

containers:
web:
rules:
ingress:
- description: From ALB to container port
from_port: 8000
to_port: 8000
protocol: tcp
# source_security_group_id set dynamically in __main__.py
egress:
- description: Allow all outbound
from_port: 0
to_port: 65535
protocol: tcp
cidr_blocks:
- 0.0.0.0/0
versioncheck:
rules:
ingress:
- description: From ALB to container port
from_port: 8000
to_port: 8000
protocol: tcp
# source_security_group_id set dynamically in __main__.py
egress:
- description: Allow all outbound
from_port: 0
to_port: 65535
protocol: tcp
cidr_blocks:
- 0.0.0.0/0
worker:
rules:
ingress: [] # Workers have no inbound traffic
egress:
- description: Allow all outbound
from_port: 0
to_port: 65535
protocol: tcp
cidr_blocks:
- 0.0.0.0/0

# =============================================================================
# ECS Scheduled Tasks (Cron Jobs)
Expand Down