Frontend Labs
⋯

Chapter 29 — Hands-on: end-to-end authorization for SecureShop

Brief

Assemble Part 5 into one working system: SPIFFE identities for every service, mTLS + audience-scoped token exchange across three hops, a ReBAC policy engine enforcing object-level authorization consistently, RLS-backed tenant isolation in the database, and resilience controls — then attack it from a compromised service and prove the blast radius is minimal.

Setup

   * kind cluster + a mesh (Istio or Linkerd) + SPIRE (or the mesh's
     built-in SPIFFE).
   * Services (any language): bff -> orders-api -> {catalog-api,
     payments-api}, plus a data-service fronting PostgreSQL.
   * OpenFGA (or SpiceDB) for the authz model.
   * Keycloak (or ory/hydra) for OIDC + token exchange.
   * PostgreSQL with RLS; Redis for rate limiting.
   * Two tenants, a handful of users with different relationships
     (owner, shared_viewer, tenant agent, tenant admin).

Tasks

   1. IDENTITY (Ch 23)
      [ ] SPIRE registration entries for every service (ns + sa selectors).
      [ ] Strict mTLS in the mesh; AuthorizationPolicies so each service
          only accepts its real callers by SPIFFE principal.
      [ ] NetworkPolicy default-deny as the defense-in-depth backstop.

   2. TOKEN PROPAGATION (Ch 24)
      [ ] User logs in -> access token (aud=bff).
      [ ] Each hop does RFC 8693 token exchange: down-scoped, aud = the
          exact next service, exp ~60s, `act` chain recorded.
      [ ] Every service validates sig/iss/exp/AUD==self; identity from the
          token, never a header.

   3. AUTHORIZATION (Ch 25)
      [ ] Model SecureShop's order/invoice/tenant relations in OpenFGA.
      [ ] A PEP in each service: check(subject=token.sub, relation,
          object) before acting; 403 -> 404.
      [ ] Keep tuples in sync from domain events (order.shared,
          agent.added, ...).
      [ ] Use a consistency token on security-sensitive reads.

   4. TENANT DATA ISOLATION (Ch 27)
      [ ] RLS + FORCE on orders/invoices; app role is non-owner,
          non-superuser, no BYPASSRLS.
      [ ] Pool sets `SET LOCAL app.tenant_id` per transaction from the
          verified claim.
      [ ] A "cross-tenant" test suite: every read path, run as tenant B,
          assert zero tenant-A rows. A canary tenant + sentinel alert.
      [ ] (bonus) per-tenant column encryption for invoice PII.

   5. RESILIENCE (Ch 28)
      [ ] Timeouts + bounded jittered retries + a retry budget on every
          hop; a circuit breaker to payments-api.
      [ ] Rate limits (per user/tenant/endpoint) + per-tenant quotas.
      [ ] Reports/exports on a bulkheaded worker pool, async for big jobs.

   6. ATTACK IT (document each)
      [ ] Compromise catalog-api (assume RCE). From it:
          - call payments-api with catalog-api's token -> 401 (aud).
          - call the DB service directly -> mTLS identity is catalog-api,
            not data-service's allowed callers -> refused; even if
            reached, RLS scopes to the current tenant and PEP denies.
          - forge `X-Tenant-Id`/`X-User-Id` -> ignored (identity from
            token).
          - read another tenant's order via the API -> OpenFGA check
            denies; RLS would too.
          - replay a captured token after 90s -> expired.
          - retry-storm payments-api -> retry budget + circuit breaker
            contain it.
      [ ] Compromise the bff. Show it can only act for the currently
          authenticated user, within exchanged scopes.
      [ ] Forget a `WHERE tenant_id` in a new data-service query -> RLS
          keeps it tenant-scoped; the cross-tenant test still fails the
          build.

Deliverable

~/sec-lab/adv/reports/p5-authz.md: the request-flow diagram with identity, token, and authz check at every hop; the OpenFGA model + representative check/deny results; the RLS config and the cross-tenant test output; the resilience settings; and the attack log showing each attempt blocked with the specific control that stopped it. Plus: "the two controls doing the most work, and what happens if each is removed".

Definition of done

  • Every inter-service call is mTLS with a verified SPIFFE identity and an audience-scoped, short-lived token.
  • Object-level authorization is enforced by one shared model, called by a PEP in every service; deny returns 404.
  • Tenant isolation is structural (RLS + FORCE + non-privileged role); "as tenant B" tests pass for every read path; a forgotten WHERE is harmless.
  • A compromised catalog-api cannot reach payments, cross tenants, forge identity, replay tokens, or storm downstreams.
  • Resilience controls (timeouts, retry budget, breaker, rate limits, quotas, bulkhead) are in place and demonstrated.

Rubric (/100)

   SPIFFE identity + mesh authz + NetworkPolicy .... 15
   Token exchange (scoped, aud-checked, act chain) . 20
   ReBAC model + PEP in every service, consistent .. 25
   Structural tenant isolation + cross-tenant tests  20
   Resilience controls demonstrated ............... 10
   Attack log: every attempt blocked, control named  5
   Report ....................................... 5

Further reading

  • Book: Microservices Security in Action (Siriwardena & Dias) — this chapter is essentially its capstone.
  • Guides: OWASP "Microservices Security Cheat Sheet"; the OpenFGA "Modeling" guide worked end-to-end; Istio "Security" tasks.
  • Reference: Google BeyondProd; the Zanzibar paper; RFC 8693.

End of Part 5 — Milestone check

  • Every service can answer "who is calling me?" with a verified SPIFFE identity, obtained with no bootstrap secret
  • I propagate end-user context across hops with audience-scoped, short-lived exchanged tokens, and every service checks aud
  • I have a single authorization model (ReBAC/Zanzibar-style) enforced by a PEP in every service, with consistency handled
  • Tenant data isolation is structural (RLS + per-tenant keys + per-store scoping), not a WHERE clause, and I have "as tenant B" tests
  • I treat availability as security: rate limits, quotas, timeouts, retry budgets, circuit breakers, bulkheads, and complexity-input limits
  • I built end-to-end authz for SecureShop and proved a compromised service has minimal blast radius

Generated from real-life-security-guide-v1.md · 8 Oct 2026 16:36