Lesson 12 · Row-Level Security & the whole chain
rls_scan, service-to-service & the recap
The guardrail that catches a missing policy, how the mesh authenticates itself — and the whole course in one view.
Your win: explain how the repo guards against a forgotten RLS policy, how internal service-to-service calls get an identity, and recite the entire auth/authz chain from memory.
The guardrail: rls_scan
RLS only protects a table if someone remembered to add the policy. A new table shipped without one is a silent cross-tenant leak — no error, just data visible to everyone. So a daily job audits it:
ScanRLS: for every table in schema public:
does it have the expected permission_check RLS policy?
if not, and it's not in migrations/public_tables.json (the allow-list) → FLAG IT
rls_scan enumerates every table and checks each has a permission_check
policy, comparing against an explicit allow-list of intentionally-public tables
(public_tables.json). A table that's neither protected nor allow-listed gets flagged.
It's the automated backstop for the one human error the whole design can't otherwise catch:
forgetting. (This is the same spirit as the deployment track's drift detectors — verify
the desired state, don't just hope for it.)
Service-to-service: identity without a human
Not every call comes from a logged-in person. When conversationmgmt consumes a Kafka event, or
one service calls another internally, there's no user JWT to verify — yet the downstream code still
needs an identity (for RLS's resource_path, if nothing else). Two mechanisms fill the
gap (both met earlier):
Faked JWT context (L4)
FakeJwtContext synthesizes a School-Admin CustomClaims from the
request's organization_id for allow-listed internal methods. The org scopes the
call; the mesh boundary is the trust.
Shamir fake tokens
GenerateFakeToken mints real, signed Manabie tokens for non-prod and testing —
a genuine token without a real login, for controlled environments.
organization_id in the request, and these methods are a small, explicit allow-list —
never a business endpoint. (Recall Lesson 4: fakeJwtCtxEndpoint ≠
ignoreAuthEndpoint; synthesized identity, not no identity.)
The edges worth remembering
Things that will trip you (or an interviewer) if you don't know them:
| Surprise | The reality |
|---|---|
| "Missing rbac entry panics at startup" | Myth. It's PermissionDenied at runtime (L5) |
Two interceptors packages | golibs (legacy) & usermgmt (live); same type names (L1, L4) |
| "Shamir" = secret sharing | No — it's a JWT authority; standard RSA signing (L3) |
| Duplicate claims in a token | manabie.* and x-hasura-* — for Go and Hasura RLS (L2) |
| Auth path depends on a flag | Unleash DecouplingUserAndAuthDB switches bob-DB vs a separate auth-DB per org |
🏛️ The whole chain, in one view
FORCE-d so even the owner is bound. 5. Defense in depth — the same
rules enforced at independent layers, with a scanner guarding the one thing humans forget.
The confused deputy & token exchange for services
The service-to-service hazard, and the standard (RFC 8693) way to carry identity between services — the ideas behind the faked-JWT design.
→ RFC 8693 — OAuth 2.0 Token Exchange (delegation/impersonation)
→ The confused deputy problem ·
in-repo cmd/server/rls_scan/rls_scan.go, internal/golibs/interceptors/internal_api.go
Check yourself (from memory)
Q1. What does rls_scan protect against?
permission_check policy (unless allow-listed). The backstop for "forgetting."
Q2. How does an internal, human-less call (e.g. a Kafka consumer) get an identity?
FakeJwtContext fabricates org-scoped claims for
allow-listed internal methods — synthesized identity, not none.
Q3. A missing rbacDecider entry causes…
Check returns
sttNoDeciderProvided at call time. No startup validation exists.
cmd/server/rls_scan, daily): enumerates
every public table, flags any without permission_check RLS (unless in
public_tables.json) — catches the forgotten-policy tenant leak.
Service-to-service: FakeJwtContext synthesizes org-scoped School-Admin
claims for allow-listed internal methods; Shamir GenerateFakeToken for non-prod. Manages
the confused-deputy risk via org-scoping + a small allow-list. The chain: authN
(Firebase → Shamir mint/JWKS → JWT{resource_path} → interceptor verify → ctx) then authZ 3 tiers:
① role (rbacDecider), ② permission scoped to a location subtree, ③ RLS
(permission_check tenant + location-aware, FORCE-d). Defense in depth;
resource_path threads all layers. Edges: rbac-panic myth, two interceptor packages,
Shamir≠secret-sharing, Hasura twin, Unleash DB-decoupling.1. In-repo: cmd/server/rls_scan/rls_scan.go, internal/golibs/database/scan_rls.go:11, internal/golibs/interceptors/internal_api.go. RFC 8693.