AWSTemplateFormatVersion: "2010-09-09"
Description: >-
  Britive Bridge v2 — full cluster (orchestrator + dynamically-spawned proxy/session
  workers) on Fargate. Creates RDS PostgreSQL, EFS (+mount targets), an NLB
  (internal by default; optional internet-facing) with TLS:443 for the web tier and
  TCP 2222/3389/3306 for native SSH/RDP/MySQL, locked to a single client CIDR,
  IAM, and the orchestrator service. Testing-ground: secrets are passed as
  parameters; delete the stack to tear everything down.

Parameters:
  ImageUri:
    Type: String
    Default: britive/bridge:latest
    Description: The Britive Bridge container image (arm64). Pull from Docker Hub (britive/bridge:<version>) or mirror it into your own ECR and reference that URI here.
  VpcId:
    Type: AWS::EC2::VPC::Id
    Default: vpc-0123456789abcdef0
  SubnetIds:
    Type: List<AWS::EC2::Subnet::Id>
    Default: "subnet-0123456789abcdef3,subnet-0123456789abcdef4,subnet-0123456789abcdef5"
    Description: Three private/NAT-routed subnets for workers, RDS, and EFS. Workers never receive public IPs; these subnets must route to Bridge target networks.
  LoadBalancerSubnetIds:
    Type: List<AWS::EC2::Subnet::Id>
    Default: "subnet-0123456789abcdef0,subnet-0123456789abcdef1,subnet-0123456789abcdef2"
    Description: Subnets for the NLB. Use private subnets for internal NLBs; use public subnets only when LoadBalancerScheme is internet-facing.
  LoadBalancerScheme:
    Type: String
    Default: internal
    AllowedValues: [internal, internet-facing]
    Description: NLB exposure. Keep internal for private deployments; choose internet-facing only for a reviewed public endpoint and restrict ClientCidr tightly.
  CertificateArn:
    Type: String
    Default: arn:aws:acm:us-west-2:123456789012:certificate/00000000-0000-0000-0000-000000000000
    Description: ACM cert for bridge.example.com (NLB TLS:443 listener).
  ClientCidr:
    Type: String
    Default: 10.0.0.0/8
    Description: Only this CIDR may reach the load balancer. For internet-facing NLBs, use a narrow office/VPN egress CIDR.
  DbPassword:
    Type: String
    NoEcho: true
  ClusterToken:
    Type: String
    NoEcho: true
  EncryptionKeyB64:
    Type: String
    NoEcho: true
    Description: base64 of 32 random bytes — shared checkout-encryption key across the cluster.
  HostKeySeed:
    Type: String
    NoEcho: true
    Description: Shared SSH host-key seed so session workers present a stable key.
  BridgeApiUrl:
    Type: String
    Default: https://bridge.example.com
    Description: Bridge API base URL the broker's checkout scripts call (the proxy endpoint).
  BrokerTenantSubdomain:
    Type: String
    Default: your-tenant
    Description: BRITIVE_BROKER_TENANT_SUBDOMAIN for the broker pool.
  BrokerAuthToken:
    Type: String
    NoEcho: true
    Default: ""
    Description: Broker pool auth token. The broker runs as an essential sidecar on the
      orchestrator task, so this must be set to a valid token — an empty/invalid token
      makes the broker exit, which cycles the whole orchestrator task.
  BrokerPrivateSubnetIds:
    Type: List<AWS::EC2::Subnet::Id>
    Default: "subnet-0123456789abcdef3,subnet-0123456789abcdef4"
    Description: Private (NAT-routed) subnets for the orchestrator+broker task — no public
      IP; egress (AWS APIs, Britive MQTT, bridge API) via the NAT gateway.
  NatEgressCidr:
    Type: String
    Default: 198.51.100.10/32
    Description: Optional NAT gateway EIP /32 allowed to reach the NLB :443 when the broker calls an internet-facing Bridge API URL.
  SshProvisionKeyPem:
    Type: String
    NoEcho: true
    Default: ""
    Description: Privileged SSH private key (PEM) the broker uses to provision Linux SSH targets (PROVISION_KEY_PEM).

Resources:
  Cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: bridge-v2

  Logs:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: /bridge/v2
      RetentionInDays: 30

  # --- Security groups ---
  LbSg:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Bridge v2 NLB ingress (single client CIDR)
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        - { IpProtocol: tcp, FromPort: 443,  ToPort: 443,  CidrIp: !Ref ClientCidr }
        - { IpProtocol: tcp, FromPort: 2222, ToPort: 2222, CidrIp: !Ref ClientCidr }
        - { IpProtocol: tcp, FromPort: 3389, ToPort: 3389, CidrIp: !Ref ClientCidr }
        - { IpProtocol: tcp, FromPort: 3306, ToPort: 3306, CidrIp: !Ref ClientCidr }
  TaskSg:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Bridge v2 tasks (orchestrator/proxy/session)
      VpcId: !Ref VpcId
  TaskSgSelf:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref TaskSg
      IpProtocol: -1
      SourceSecurityGroupId: !Ref TaskSg
  TaskSgFromLb:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref TaskSg
      IpProtocol: tcp
      FromPort: 2222
      ToPort: 9091          # covers 2222, 3306, 3389, 8080 (data) + 9091 (health)
      SourceSecurityGroupId: !Ref LbSg
  DbSg:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Bridge v2 RDS PostgreSQL
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        - { IpProtocol: tcp, FromPort: 5432, ToPort: 5432, SourceSecurityGroupId: !Ref TaskSg }
  EfsSg:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Bridge v2 EFS (NFS from tasks)
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        - { IpProtocol: tcp, FromPort: 2049, ToPort: 2049, SourceSecurityGroupId: !Ref TaskSg }
  # The broker runs as a sidecar on the orchestrator task and shares TaskSg
  # (default allow-all egress: Britive MQTT, the bridge API, and the
  # SSH/WinRM/MySQL targets it provisions). The provisioning-target security
  # groups (managed outside this stack) allow TaskSg as an ingress source.
  # This rule lets the broker reach the NLB :443 on internal deployments.
  LbSgFromTasks443:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref LbSg
      IpProtocol: tcp
      FromPort: 443
      ToPort: 443
      SourceSecurityGroupId: !Ref TaskSg
  # This rule supports internet-facing deployments where BridgeApiUrl resolves to
  # the public NLB name and the broker reaches it through a NAT gateway EIP.
  LbSgFromBrokerNat:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref LbSg
      IpProtocol: tcp
      FromPort: 443
      ToPort: 443
      CidrIp: !Ref NatEgressCidr

  # --- RDS PostgreSQL (mandatory datastore) ---
  DbSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: Bridge v2 DB subnets
      SubnetIds: !Ref SubnetIds
  Db:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      Engine: postgres
      DBInstanceClass: db.t4g.micro
      AllocatedStorage: "20"
      StorageType: gp3
      DBName: bridge
      MasterUsername: bridge
      MasterUserPassword: !Ref DbPassword
      DBSubnetGroupName: !Ref DbSubnetGroup
      VPCSecurityGroups: [!Ref DbSg]
      PubliclyAccessible: false
      MultiAZ: false
      BackupRetentionPeriod: 0

  # --- EFS for recordings (+ a mount target per subnet) ---
  RecordingsFs:
    Type: AWS::EFS::FileSystem
    Properties:
      Encrypted: true
  RecordingsAp:
    Type: AWS::EFS::AccessPoint
    Properties:
      FileSystemId: !Ref RecordingsFs
      PosixUser: { Uid: "102", Gid: "103" }
      RootDirectory:
        Path: /recordings
        CreationInfo: { OwnerUid: "102", OwnerGid: "103", Permissions: "0775" }
  MountA:
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref RecordingsFs
      SubnetId: !Select [0, !Ref SubnetIds]
      SecurityGroups: [!Ref EfsSg]
  MountB:
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref RecordingsFs
      SubnetId: !Select [1, !Ref SubnetIds]
      SecurityGroups: [!Ref EfsSg]
  MountC:
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref RecordingsFs
      SubnetId: !Select [2, !Ref SubnetIds]
      SecurityGroups: [!Ref EfsSg]

  # --- IAM ---
  ExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement: [{ Effect: Allow, Principal: { Service: ecs-tasks.amazonaws.com }, Action: sts:AssumeRole }]
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
  WorkerTaskRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement: [{ Effect: Allow, Principal: { Service: ecs-tasks.amazonaws.com }, Action: sts:AssumeRole }]
  OrchestratorTaskRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement: [{ Effect: Allow, Principal: { Service: ecs-tasks.amazonaws.com }, Action: sts:AssumeRole }]
      Policies:
        - PolicyName: bridge-orchestrator
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - { Effect: Allow, Action: [ecs:RunTask, ecs:StopTask, ecs:DescribeTasks, ecs:ListTasks, ecs:TagResource, ecs:DescribeTaskDefinition], Resource: "*" }
              - { Effect: Allow, Action: iam:PassRole, Resource: [!GetAtt ExecutionRole.Arn, !GetAtt WorkerTaskRole.Arn] }
              - { Effect: Allow, Action: [elasticloadbalancing:RegisterTargets, elasticloadbalancing:DeregisterTargets, elasticloadbalancing:DescribeTargetHealth], Resource: "*" }
              - { Effect: Allow, Action: [ecr:DescribeImages], Resource: "*" }
              - { Effect: Allow, Action: ec2:DescribeNetworkInterfaces, Resource: "*" }

  # --- Session worker task def (runs the protocol session + recording; heavier;
  #     read-write recordings volume). The orchestrator spawns these for RoleSession. ---
  SessionTaskDef:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: bridge-v2-session
      RequiresCompatibilities: [FARGATE]
      NetworkMode: awsvpc
      Cpu: "2048"
      Memory: "4096"
      RuntimePlatform: { CpuArchitecture: ARM64, OperatingSystemFamily: LINUX }
      ExecutionRoleArn: !GetAtt ExecutionRole.Arn
      TaskRoleArn: !GetAtt WorkerTaskRole.Arn
      Volumes:
        - Name: recordings
          EFSVolumeConfiguration:
            FilesystemId: !Ref RecordingsFs
            TransitEncryption: ENABLED
            AuthorizationConfig: { AccessPointId: !Ref RecordingsAp, IAM: DISABLED }
      ContainerDefinitions:
        - Name: bridge
          Image: !Ref ImageUri
          MountPoints: [{ SourceVolume: recordings, ContainerPath: /data/recordings, ReadOnly: false }]
          PortMappings:
            - { ContainerPort: 8080, Protocol: tcp }
            - { ContainerPort: 2222, Protocol: tcp }
            - { ContainerPort: 3389, Protocol: tcp }
            - { ContainerPort: 3306, Protocol: tcp }
            - { ContainerPort: 9091, Protocol: tcp }
          Environment:
            - { Name: BRIDGE_ROLE, Value: session }
            - { Name: BRIDGE_CONFIG, Value: /etc/britive-bridge/aws.yaml }
            - { Name: BRIDGE_DATASTORE_HOST, Value: !GetAtt Db.Endpoint.Address }
            - { Name: BRIDGE_DATASTORE_PORT, Value: !GetAtt Db.Endpoint.Port }
            - { Name: BRIDGE_DATASTORE_USER, Value: bridge }
            - { Name: BRIDGE_DATASTORE_PASSWORD, Value: !Ref DbPassword }
            - { Name: BRIDGE_DATASTORE_NAME, Value: bridge }
            - { Name: BRIDGE_DATASTORE_SSLMODE, Value: require }
            - { Name: BRIDGE_CLUSTER_TOKEN, Value: !Ref ClusterToken }
            - { Name: BRIDGE_ENCRYPTION_KEY_B64, Value: !Ref EncryptionKeyB64 }
            - { Name: BRIDGE_HOST_KEY_SEED, Value: !Ref HostKeySeed }
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref Logs
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: session

  # --- Proxy worker task def (NLB-fronting edge + web tier; relays to session
  #     workers; lighter; serves playback + the recordings UI. The volume is
  #     read-write because the proxy transcodes session recordings to the
  #     downloadable video on demand (POST /api/sessions/{id}/bundle).
  #     The orchestrator spawns these for RoleProxy. ---
  ProxyTaskDef:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: bridge-v2-proxy
      RequiresCompatibilities: [FARGATE]
      NetworkMode: awsvpc
      Cpu: "1024"
      Memory: "2048"
      RuntimePlatform: { CpuArchitecture: ARM64, OperatingSystemFamily: LINUX }
      ExecutionRoleArn: !GetAtt ExecutionRole.Arn
      TaskRoleArn: !GetAtt WorkerTaskRole.Arn
      Volumes:
        - Name: recordings
          EFSVolumeConfiguration:
            FilesystemId: !Ref RecordingsFs
            TransitEncryption: ENABLED
            AuthorizationConfig: { AccessPointId: !Ref RecordingsAp, IAM: DISABLED }
      ContainerDefinitions:
        - Name: bridge
          Image: !Ref ImageUri
          MountPoints: [{ SourceVolume: recordings, ContainerPath: /data/recordings, ReadOnly: false }]
          PortMappings:
            - { ContainerPort: 8080, Protocol: tcp }
            - { ContainerPort: 2222, Protocol: tcp }
            - { ContainerPort: 3389, Protocol: tcp }
            - { ContainerPort: 3306, Protocol: tcp }
            - { ContainerPort: 9091, Protocol: tcp }
          Environment:
            - { Name: BRIDGE_ROLE, Value: proxy }
            - { Name: BRIDGE_CONFIG, Value: /etc/britive-bridge/aws.yaml }
            - { Name: BRIDGE_DATASTORE_HOST, Value: !GetAtt Db.Endpoint.Address }
            - { Name: BRIDGE_DATASTORE_PORT, Value: !GetAtt Db.Endpoint.Port }
            - { Name: BRIDGE_DATASTORE_USER, Value: bridge }
            - { Name: BRIDGE_DATASTORE_PASSWORD, Value: !Ref DbPassword }
            - { Name: BRIDGE_DATASTORE_NAME, Value: bridge }
            - { Name: BRIDGE_DATASTORE_SSLMODE, Value: require }
            - { Name: BRIDGE_CLUSTER_TOKEN, Value: !Ref ClusterToken }
            - { Name: BRIDGE_ENCRYPTION_KEY_B64, Value: !Ref EncryptionKeyB64 }
            - { Name: BRIDGE_HOST_KEY_SEED, Value: !Ref HostKeySeed }
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref Logs
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: proxy

  # --- NLB (internal by default; optionally internet-facing, SG-locked) ---
  Nlb:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Type: network
      Scheme: !Ref LoadBalancerScheme
      Subnets: !Ref LoadBalancerSubnetIds
      SecurityGroups: [!Ref LbSg]
  # All target groups enable preserve_client_ip so workers see the real end-user
  # source IP (recorded as the session client IP; the web tier also propagates it
  # downstream via X-Forwarded-For). Safe with no TaskSg change because the NLB has
  # a security group (LbSg): load-balanced traffic to targets is still evaluated
  # against LbSg (TaskSgFromLb), not the client IP.
  WebTg:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      VpcId: !Ref VpcId
      Protocol: TCP
      Port: 8080
      TargetType: ip
      HealthCheckProtocol: HTTP
      HealthCheckPort: "9091"
      HealthCheckPath: /readyz
      TargetGroupAttributes: [{ Key: preserve_client_ip.enabled, Value: "true" }]
  SshTg:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      VpcId: !Ref VpcId
      Protocol: TCP
      Port: 2222
      TargetType: ip
      HealthCheckProtocol: HTTP
      HealthCheckPort: "9091"
      HealthCheckPath: /readyz
      TargetGroupAttributes: [{ Key: preserve_client_ip.enabled, Value: "true" }]
  RdpTg:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      VpcId: !Ref VpcId
      Protocol: TCP
      Port: 3389
      TargetType: ip
      HealthCheckProtocol: HTTP
      HealthCheckPort: "9091"
      HealthCheckPath: /readyz
      TargetGroupAttributes: [{ Key: preserve_client_ip.enabled, Value: "true" }]
  MysqlTg:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      VpcId: !Ref VpcId
      Protocol: TCP
      Port: 3306
      TargetType: ip
      HealthCheckProtocol: HTTP
      HealthCheckPort: "9091"
      HealthCheckPath: /readyz
      TargetGroupAttributes: [{ Key: preserve_client_ip.enabled, Value: "true" }]
  WebListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref Nlb
      Protocol: TLS
      Port: 443
      SslPolicy: ELBSecurityPolicy-TLS13-1-2-2021-06
      Certificates: [{ CertificateArn: !Ref CertificateArn }]
      DefaultActions: [{ Type: forward, TargetGroupArn: !Ref WebTg }]
  SshListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref Nlb
      Protocol: TCP
      Port: 2222
      DefaultActions: [{ Type: forward, TargetGroupArn: !Ref SshTg }]
  RdpListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref Nlb
      Protocol: TCP
      Port: 3389
      DefaultActions: [{ Type: forward, TargetGroupArn: !Ref RdpTg }]
  MysqlListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref Nlb
      Protocol: TCP
      Port: 3306
      DefaultActions: [{ Type: forward, TargetGroupArn: !Ref MysqlTg }]

  # --- Orchestrator task def + service ---
  #     One container: the orchestrator (reconciler + leader election) AND the
  #     Britive Broker, both from the unified bridge image (the broker is a Go binary
  #     baked in and launched by the entrypoint's orchestrator branch under
  #     supervisord). A broker is always co-located with the single always-on
  #     orchestrator. The task runs in the NAT'd private subnets (no public IP): the
  #     orchestrator reaches AWS APIs + Britive MQTT via NAT, and the broker reaches
  #     the bridge API through the NLB security-group rules above.
  OrchestratorTaskDef:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: bridge-v2-orchestrator
      RequiresCompatibilities: [FARGATE]
      NetworkMode: awsvpc
      Cpu: "1024"
      Memory: "2048"
      RuntimePlatform: { CpuArchitecture: ARM64, OperatingSystemFamily: LINUX }
      ExecutionRoleArn: !GetAtt ExecutionRole.Arn
      TaskRoleArn: !GetAtt OrchestratorTaskRole.Arn
      ContainerDefinitions:
        - Name: bridge
          Image: !Ref ImageUri
          Essential: true
          Environment:
            - { Name: BRIDGE_ROLE, Value: orchestrator }
            - { Name: BRIDGE_CONFIG, Value: /etc/britive-bridge/aws.yaml }
            - { Name: BRIDGE_DATASTORE_HOST, Value: !GetAtt Db.Endpoint.Address }
            - { Name: BRIDGE_DATASTORE_PORT, Value: !GetAtt Db.Endpoint.Port }
            - { Name: BRIDGE_DATASTORE_USER, Value: bridge }
            - { Name: BRIDGE_DATASTORE_PASSWORD, Value: !Ref DbPassword }
            - { Name: BRIDGE_DATASTORE_NAME, Value: bridge }
            - { Name: BRIDGE_DATASTORE_SSLMODE, Value: require }
            - { Name: BRIDGE_CLUSTER_TOKEN, Value: !Ref ClusterToken }
            - { Name: BRIDGE_ENCRYPTION_KEY_B64, Value: !Ref EncryptionKeyB64 }
            - { Name: BRIDGE_HOST_KEY_SEED, Value: !Ref HostKeySeed }
            # Worker provisioning config. Spawned proxy/session workers always
            # use private IPs in the worker/data subnets; even an internet-facing
            # NLB forwards to those private target IPs inside the VPC.
            - { Name: CLUSTER_NAME, Value: !Ref Cluster }
            - { Name: BRIDGE_SUBNETS, Value: !Join [",", !Ref SubnetIds] }
            - { Name: BRIDGE_SECURITY_GROUPS, Value: !Ref TaskSg }
            - { Name: BRIDGE_ASSIGN_PUBLIC_IP, Value: DISABLED }
            - { Name: BRIDGE_TASKDEF_SESSION, Value: !Ref SessionTaskDef }
            - { Name: BRIDGE_TASKDEF_PROXY, Value: !Ref ProxyTaskDef }
            - { Name: BRIDGE_PROXY_TG_ARNS, Value: !Join [",", [!Ref WebTg, !Ref SshTg, !Ref RdpTg, !Ref MysqlTg]] }
            # Britive Broker (co-located, same container). The entrypoint renders the
            # broker config from these and runs it under supervisord alongside the
            # orchestrator. The downloaded checkout scripts use BRIDGE_API_URL +
            # BRIDGE_CLUSTER_TOKEN + PROVISION_KEY_PEM to provision targets / register
            # checkouts. An empty BRITIVE_BROKER_AUTH_TOKEN leaves the broker off.
            - { Name: BRITIVE_BROKER_TENANT_SUBDOMAIN, Value: !Ref BrokerTenantSubdomain }
            - { Name: BRITIVE_BROKER_AUTH_TOKEN, Value: !Ref BrokerAuthToken }
            - { Name: BRIDGE_API_URL, Value: !Ref BridgeApiUrl }
            - { Name: BRIDGE_TLS_INSECURE, Value: "false" }
            - { Name: PROVISION_KEY_PEM, Value: !Ref SshProvisionKeyPem }
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref Logs
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: orchestrator

  OrchestratorService:
    Type: AWS::ECS::Service
    DependsOn: [WebListener, SshListener, RdpListener, MysqlListener, MountA, MountB, MountC]
    Properties:
      Cluster: !Ref Cluster
      DesiredCount: 1
      LaunchType: FARGATE
      TaskDefinition: !Ref OrchestratorTaskDef
      NetworkConfiguration:
        AwsvpcConfiguration:
          # Private (NAT-routed) subnets, no public IP: AWS APIs and Britive MQTT
          # egress through NAT, while the bridge API is reachable through the NLB
          # security-group rule above. Provisioning-target SGs allow TaskSg.
          Subnets: !Ref BrokerPrivateSubnetIds
          SecurityGroups: [!Ref TaskSg]
          AssignPublicIp: DISABLED

Outputs:
  NlbDns:
    Description: Point bridge.example.com at this; web/UI on :443 (TLS), native SSH :2222, RDP :3389, MySQL :3306.
    Value: !GetAtt Nlb.DNSName
  NlbHostedZoneId:
    Value: !GetAtt Nlb.CanonicalHostedZoneID
  DbEndpoint:
    Value: !GetAtt Db.Endpoint.Address
