Skip to content

Generated blocks

Never draw a piece’s connections or a role’s permissions by hand: generate them from apps/book/src/data. Then the map, the chapters and the appendix can never disagree, and pnpm book:check proves every id exists and every generated diagram renders.

Import them from ~/components. The ids are the component, role and flow ids in src/data.

<WhereItSits piece="alb" /> draws the piece with up to 6 neighbours and the rule on each arrow. Add layers={["network", "security"]} to show only some layers.

flowchart LR
  alb["Application Load Balancer"]
  public_subnets["Public subnets"]
  browser["Browser"]
  https_listener["HTTPS listener"]
  cloudfront["CloudFront distribution"]
  cloudfront_listener["HTTP listener with the origin header"]
  alb_sg["ALB security group"]
  alb -->|lives in| public_subnets
  browser -->|TCP 443| alb
  alb -->|TCP 443| https_listener
  cloudfront -->|TCP 80| alb
  alb -->|TCP 80| cloudfront_listener
  alb -->|uses| alb_sg
  more["+1 more: see the table"]
  alb -.- more
  classDef focus stroke-width:3px
  class alb focus
  classDef off stroke-dasharray:5 5
  class alb,cloudfront,cloudfront_listener off
Where Application Load Balancer (ALB) sits: 7 connections, generated from the book's data. A dashed border means the piece is switched off in the committed config (infra/Pulumi.dev.yaml). On small screens, scroll the diagram sideways to read its labels.

<ConnectionTable piece="alb" /> lists every connection of the piece, both directions, with the exact rule. layer="security" without piece lists one layer of the whole map.

From → toWhat happensWhat allows it
Application Load Balancer → Public subnetsonly when entryEnabled = trueone ALB node in each public subnet
  • config internal: false, subnets = both public subnetscreateAlb
Browser → Application Load Balanceronly when cdnEnabled = falseHTTPS on port 443; plain HTTP on port 80 only gets a redirect
  • route public route table: 0.0.0.0/0 → minitickets-dev-igw. The task has a public IP; there is no NAT gateway and no VPC endpoint.createNetwork
  • sg-rule alb-sg inbound rule minitickets-dev-alb-in-443-0: TCP 443 from allowedCidrs (default 0.0.0.0/0)createAlbPublicIngress
  • sg-rule alb-sg inbound rule minitickets-dev-alb-in-80-0: TCP 80 from allowedCidrs, for the 301 redirect onlycreateAlbPublicIngress
  • certificate the :443 listener presents the ACM certificate for minitickets.theversehub.increateHttpsListener
Application Load Balancer → HTTPS listeneronly when cdnEnabled = falsethe ALB hands each request to the listener for its port
  • listener-rule listener :443 HTTPS, TLS policy ELBSecurityPolicy-TLS13-1-2-2021-06; listener :80 HTTP answers 301 → https://createHttpsListener
CloudFront distribution → Application Load Balanceronly when cdnEnabled = truefetches from the origin over plain HTTP on port 80, adding the X-Origin-Verify header
  • sg-rule alb-sg inbound rule minitickets-dev-alb-in-80: TCP 80 from the AWS managed prefix list com.amazonaws.global.cloudfront.origin-facingcreateSecurityGroups
  • secret origin customHeaders: X-Origin-Verify = the origin secretcreateCdn
Application Load Balancer → HTTP listener with the origin headeronly when cdnEnabled = truethe ALB hands each request to its :80 listener
  • listener-rule listener :80 HTTP, default action: fixed response 403 ForbiddencreateListener
Application Load Balancer → ALB security grouponly when entryEnabled = truethe ALB's network interfaces use the ALB security group
Application Load Balancer → CloudWatch alarmsonly when entryEnabled = truetarget 5xx + ALB 5xx ≥ 5 in 5 minutes → alb-5xx
  • metric AWS/ApplicationELB FILL(HTTPCode_Target_5XX_Count, 0) + FILL(HTTPCode_ELB_5XX_Count, 0), period 300 salarmSpecs

<RolePermissions role="api-task-role" /> shows the trust policy and every permission statement, with “why only this”.

Who may assume API task role: API task definition (Principal ecs-tasks.amazonaws.com; condition aws:SourceAccount = our account (stops the confused deputy problem))

What it may do:

OnActionsPolicyWhy only this
Booking-events SNS topicsns:Publishidentity send-jobs createIamRolesThe outbox relay publishes booking events. The api cannot read either queue: SNS delivers to them with the queues' own policies.
Tickets tabledynamodb:Queryidentity ReadTickets createIamRolesMy tickets is one Query on the userId partition. No Scan, and no writes: only the tickets Lambda writes tickets.
Ticket assets buckettickets/* onlys3:GetObjectidentity ReadTicketPngs createIamRolesA presigned ticket GET works with the signer's permissions, so the api role must be allowed to read the ticket PNG it signs.
Ticket assets bucketposters/* and poster-uploads/* onlys3:GetObjectidentity ReadPosters createIamRolesThe api reads staged uploads to check them before publishing, and reads poster objects. This separate statement grants no ticket writes.
Ticket assets bucketposters/* and poster-uploads/* onlys3:PutObjectif s3:x-amz-server-side-encryption = AES256identity WritePosters createIamRolesThe browser's presigned poster PUT and the copy to posters/ run with this role. It can never overwrite tickets/.
API ECS serviceResource * (these actions cannot be limited to one resource)ssmmessages:CreateControlChannelssmmessages:CreateDataChannelssmmessages:OpenControlChannelssmmessages:OpenDataChannelidentity ecs-exec createIamRolesECS Exec opens a debug shell inside the api task, which can reach RDS and Valkey from inside the VPC.

<WhoCanTouch piece="ticket-assets-bucket" /> lists every identity and resource policy that names the piece.

WhoActionsPolicyWhy only this
API task roletickets/* onlys3:GetObjectidentity ReadTicketPngs createIamRolesA presigned ticket GET works with the signer's permissions, so the api role must be allowed to read the ticket PNG it signs.
API task roleposters/* and poster-uploads/* onlys3:GetObjectidentity ReadPosters createIamRolesThe api reads staged uploads to check them before publishing, and reads poster objects. This separate statement grants no ticket writes.
API task roleposters/* and poster-uploads/* onlys3:PutObjectif s3:x-amz-server-side-encryption = AES256identity WritePosters createIamRolesThe browser's presigned poster PUT and the copy to posters/ run with this role. It can never overwrite tickets/.
Tickets Lambda execution roletickets/* onlys3:PutObjectidentity createTicketRoleWrite the QR PNGs. It cannot read or touch posters.
All principalsthe ticket-assets bucket ARN and every object ARN under itDeny s3:*if aws:SecureTransport = falseresource minitickets-dev-ticket-assets-tls bucket policy createTicketStorageEvery caller, including anonymous requests, is refused over plain HTTP. This deny overrides an identity grant or a presigned URL; use HTTPS.
GitHub deploy rolethe bucket and its objectss3:*identity TicketAssets createCicdPulumi manages the bucket and its settings.
GitHub deploy roleResource *; all actions on all resourcesDeny *if aws:RequestedRegion != us-east-1identity DenyOtherRegions createCicdAn explicit Deny beats every Allow: requests to other region endpoints are refused. Global services such as IAM and CloudFront use us-east-1 endpoints and pass this condition.

<FlowSteps flow="why-can-it-connect" /> lists a flow’s steps, with “if it breaks” for each step that has a failure branch. The map animates the same steps.

Three questions decide every connection: Reach (can the packet get there?), Allowed (do the rules let it in?), Password (is the secret right?).

  1. API ECS service → API security group. Allowed, part 1: every API task uses the api security group.
  2. RDS Postgres → Database security group. Allowed, part 2: Postgres uses the db security group. Its only inbound rule allows TCP 5432 from the api security group.If it breaks: The rule names another source: the packets are dropped and the api times out. Look: EC2 → Security Groups → minitickets-dev-db-sg → Inbound rules: Source must be the api group.
  3. API ECS service → RDS Postgres. Reach: the task is in a public subnet, Postgres in a private one. Both are in the VPC, and the local route 10.0.0.0/16 connects them. No internet route is needed.If it breaks: The task uses a different security group, so the db rule does not match. Look: scripts/run-migrations.sh (securityGroups=[$API_SG]) and the stopped task's network settings.
  4. API ECS service → RDS master password secret. Password: when the task started, ECS used the execution role to read the RDS master secret into DB_USER and DB_PASSWORD.If it breaks: The execution role may not read the secret: the task never starts. Look: ECS → the stopped task → Stopped reason; IAM → the role → read-secrets.
  5. API ECS service → RDS Postgres. TLS: RDS forces SSL and the api verifies the server certificate. Connected.If it breaks: A client without SSL is refused. Look: apps/api/src/config/database.ts (SSL settings) and the api log.

Read this book online: https://book.theversehub.in/