diff --git a/infra/pulumi/__main__.py b/infra/pulumi/__main__.py index 546af77d46a3..bb87c9cb4d05 100755 --- a/infra/pulumi/__main__.py +++ b/infra/pulumi/__main__.py @@ -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, ) @@ -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, ) diff --git a/infra/pulumi/config.stage.yaml b/infra/pulumi/config.stage.yaml index 8b6b0d957695..963ff6d5c3a2 100644 --- a/infra/pulumi/config.stage.yaml +++ b/infra/pulumi/config.stage.yaml @@ -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)