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.
Where it sits
Section titled “Where it sits”<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
Connections and what allows them
Section titled “Connections and what allows them”<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 → to | What happens | What allows it |
|---|---|---|
| Application Load Balancer → Public subnetsonly when entryEnabled = true | one ALB node in each public subnet |
|
| Browser → Application Load Balanceronly when cdnEnabled = false | HTTPS on port 443; plain HTTP on port 80 only gets a redirect |
|
| Application Load Balancer → HTTPS listeneronly when cdnEnabled = false | the ALB hands each request to the listener for its port |
|
| CloudFront distribution → Application Load Balanceronly when cdnEnabled = true | fetches from the origin over plain HTTP on port 80, adding the X-Origin-Verify header |
|
| Application Load Balancer → HTTP listener with the origin headeronly when cdnEnabled = true | the ALB hands each request to its :80 listener |
|
| Application Load Balancer → ALB security grouponly when entryEnabled = true | the ALB's network interfaces use the ALB security group |
|
| Application Load Balancer → CloudWatch alarmsonly when entryEnabled = true | target 5xx + ALB 5xx ≥ 5 in 5 minutes → alb-5xx |
|
A role: who may assume it, what it may do
Section titled “A role: who may assume it, what it may do”<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
What it may do:
| On | Actions | Policy | Why only this |
|---|---|---|---|
| Booking-events SNS topic | sns:Publish | identity send-jobs createIamRoles | The outbox relay publishes booking events. The api cannot read either queue: SNS delivers to them with the queues' own policies. |
| Tickets table | dynamodb:Query | identity ReadTickets createIamRoles | My tickets is one Query on the userId partition. No Scan, and no writes: only the tickets Lambda writes tickets. |
| Ticket assets buckettickets/* only | s3:GetObject | identity ReadTicketPngs createIamRoles | A 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/* only | s3:GetObject | identity ReadPosters createIamRoles | The 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/* only | s3:PutObjectif s3:x-amz-server-side-encryption = AES256 | identity WritePosters createIamRoles | The 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:OpenDataChannel | identity ecs-exec createIamRoles | ECS Exec opens a debug shell inside the api task, which can reach RDS and Valkey from inside the VPC. |
Who can touch a piece
Section titled “Who can touch a piece”<WhoCanTouch piece="ticket-assets-bucket" /> lists every identity and resource policy that
names the piece.
| Who | Actions | Policy | Why only this |
|---|---|---|---|
| API task roletickets/* only | s3:GetObject | identity ReadTicketPngs createIamRoles | A 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/* only | s3:GetObject | identity ReadPosters createIamRoles | The 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/* only | s3:PutObjectif s3:x-amz-server-side-encryption = AES256 | identity WritePosters createIamRoles | The browser's presigned poster PUT and the copy to posters/ run with this role. It can never overwrite tickets/. |
| Tickets Lambda execution roletickets/* only | s3:PutObject | identity createTicketRole | Write the QR PNGs. It cannot read or touch posters. |
| All principalsthe ticket-assets bucket ARN and every object ARN under it | Deny s3:*if aws:SecureTransport = false | resource minitickets-dev-ticket-assets-tls bucket policy createTicketStorage | Every 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 objects | s3:* | identity TicketAssets createCicd | Pulumi manages the bucket and its settings. |
| GitHub deploy roleResource *; all actions on all resources | Deny *if aws:RequestedRegion != us-east-1 | identity DenyOtherRegions createCicd | An 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. |
A flow, step by step
Section titled “A flow, step by step”<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?).
- API ECS service → API security group. Allowed, part 1: every API task uses the api security group.
- 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.
- 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.
- 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.
- 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/