Distributed Systems Security and Trust Questions
Security problems that exist because a system is distributed: keeping trust state correct while it propagates across many services, clusters and regions. Covers credential, token and certificate revocation under eventual consistency and network partitions; fleet-wide rotation of signing keys, secrets and trust anchors without outages (key rollover and grace windows, canary rotation, recovery from a compromised root); distributed authorization (replicated policy decision points, cached decisions, fail-open versus fail-closed when an auth dependency degrades); propagating caller identity and permissions through service call chains; tamper-evident audit trails across services and regions (hash chains, Merkle proofs, ordering events with imperfect clocks); Byzantine and partially trusted participants; cross-cluster and cross-organization trust federation; securing shared distributed components such as caches and message brokers against injection, replay and cross-tenant access; protecting data in transit across region boundaries; and tenant isolation as a security blast-radius boundary. Steady-state mTLS, service-mesh identity and network segmentation mechanics are covered by zero-trust service-to-service security; single-system cryptography and KMS basics by applied cryptography.
Your organization runs across 4 Kubernetes clusters, 3 cloud providers, and several third-party SaaS integrations, and needs a zero-trust architecture spanning all of them. Requirements: per-service identity, cross-org trust federation, least-privilege access, full auditability, and the ability to revoke cross-org access within 30 seconds. Describe your trust model (federated vs centralized CA), certificate issuance and attestation flows, policy enforcement points, and the SRE operational model for rotation, emergency revocation, and audits.
Sample Answer
Direct answer
Zero trust means no request is trusted because of where it comes from (inside a VPC, Virtual Private Cloud, or a cluster network); every call must prove who is calling and be checked against policy. For 4 clusters, 3 clouds and external SaaS, I would use a federated trust model: each cluster or cloud is its own trust domain with its own issuing CA (certificate authority) whose keys sit in that environment's HSM (hardware security module) or KMS (key management service), and domains trust each other by exchanging signed trust bundles, the set of CA certificates a domain has agreed to accept as valid issuers, not by sharing one global root. Workloads get short-lived, attested identities (SPIFFE IDs, a workload identity standard defined below, issued by SPIRE, its reference implementation), enforcement happens at proxies and gateways that pull policy continuously, and the 30-second cross-org revocation is met by pushing a deny policy to enforcement points and cutting live connections, because certificate revocation lists are far too slow for a 30-second promise.
Trust model: federated vs centralized CA
| Centralized CA (one root, one issuer) | Federated (one CA per trust domain, bundles exchanged) | |
|---|---|---|
| Blast radius of a CA compromise | Everything, everywhere | One domain; others drop its bundle |
| Cross-cloud latency / dependency | Every issuance crosses clouds | Issuance is local to each domain |
| Partner / SaaS fit | Partners must trust your root | Natural: they are just another trust domain |
| Operational effort | One thing to run | Bundle distribution and policy per domain |
Commit: federated, with one offline root of your own that signs each internal domain's intermediate, a subordinate CA certificate, signed by your root, that does the actual day-to-day issuing so the root itself can stay offline and rarely touched (so your own domains can share one root of trust for convenience) and separate, explicit federation relationships with external organisations, whose CAs you never chain to your root. What would flip it: a single small cloud estate with no external partners, where one CA is simpler and the blast-radius argument is weak.
SPIFFE (Secure Production Identity Framework For Everyone) is the open standard for workload identity: every workload gets an ID such as spiffe://prod.aws.example.com/ns/payments/sa/api, delivered as an SVID (SPIFFE Verifiable Identity Document), usually a short-lived X.509 certificate. SPIRE (the SPIFFE Runtime Environment) is the reference implementation that issues them and supports federation between trust domains.
flowchart TB
subgraph Org[Your organisation]
Root[(Offline root, HSM)]
A[SPIRE: AWS clusters] --- B[SPIRE: GCP cluster]
B --- C[SPIRE: Azure cluster]
Root -.signs intermediate.-> A & B & C
end
P[Partner trust domain] <-- "bundle exchange + policy" --> A
S[SaaS via OIDC federation] <-- "short-lived tokens" --> EG[Egress gateway]
A --> PEP[Envoy sidecars / gateways + policy engine]
Issuance and attestation flows
Attestation is how the issuer decides a workload deserves an identity, based on evidence it cannot fake, rather than on a secret someone copied into a config file.
- Node attestation: the SPIRE agent on a node proves what machine it is using the platform's own signed evidence, not a secret it holds itself: a Kubernetes projected service account token (a short-lived, audience-scoped token the cluster mints for that pod), an AWS instance identity document (a signed description of the EC2 instance that AWS itself vouches for), a GCP instance identity token (Google Cloud's equivalent signed statement), or an Azure managed identity (an identity Azure attaches to the VM or resource automatically, with no credential for anyone to copy). All four do the same job, prove the hardware or platform is who it claims to be, using whichever evidence that cloud or cluster natively signs.
- Workload attestation: the agent inspects the calling process (namespace, service account, image digest) and matches it to a registration entry.
- Issuance: the SPIRE server signs a short-lived SVID (1 hour here); the agent rotates it at half-life, meaning it requests a fresh one once 30 minutes (half of the 1-hour lifetime) have elapsed, so there is always a safety margin before the old one expires and workloads never hold a long-lived secret.
- To SaaS: most SaaS providers do not accept mTLS (mutual TLS: unlike ordinary TLS, both sides present a certificate and verify each other, not just the client verifying the server) from your workloads. Use OIDC workload identity federation (OIDC, OpenID Connect, is the identity layer on OAuth 2.0): the workload presents its JWT-SVID, its SPIFFE identity encoded as a JWT (JSON Web Token, a signed, self-contained token format) instead of the X.509 certificate form used inside the mesh, to the SaaS provider's or cloud's token service and receives a short-lived access token. No static API keys.
Load check: 4 clusters x 5,000 workloads each = 20,000 identities; rotating 1-hour SVIDs at half-life means 20,000 / 1,800 s, about 11 signatures per second. Trivial for any issuer; the real risk is a thundering herd, a burst of workloads all renewing at once (for example every SVID issued at the same moment during an outage recovery, so they all hit half-life together) that spikes load on the issuer, so add jitter to renewals.
Least privilege and policy enforcement points
- PEPs (policy enforcement points) are where a request is allowed or denied: Envoy (an open-source proxy) sidecars, a small proxy process deployed alongside each workload container that intercepts its traffic, or per-node proxies for east-west traffic (service-to-service calls inside the environment, as opposed to north-south traffic entering or leaving it), ingress gateways for inbound partner traffic, an egress gateway for all calls to SaaS and partners.
- Policy: authorisation rules written against SPIFFE IDs ("
payments/apimayPOST /chargeonledger"), evaluated locally by a policy engine such as OPA (Open Policy Agent), with default deny. - Distribution: policy is compiled centrally and pushed to proxies over xDS, Envoy's family of discovery APIs that lets a proxy pull its routing, cluster and policy configuration from a central control plane instead of reading a static file, so a change reaches every proxy in seconds, not on the next deploy.
Meeting 30-second cross-org revocation
X.509 revocation (CRLs, certificate revocation lists, and OCSP, the Online Certificate Status Protocol) is fetched and cached for hours, and short SVIDs of even 5 minutes cannot meet 30 seconds. So revocation is a policy action, not a certificate action:
| Step | Mechanism | Budget |
|---|---|---|
| Revoke command recorded | Control plane writes "deny trust domain partner.com" | 1 s |
| Push to all PEPs | xDS push to every proxy and gateway in all 4 clusters | p99 (99th percentile) 5 s |
| Kill existing connections | Proxies drain and close connections from/to that domain | 10 s |
| Remove federation bundle | Stop trusting the partner CA for new handshakes | in parallel |
| Verify | Synthetic partner-identity probe must get denied everywhere | 10 s |
| Total | about 26 s, inside 30 |
The connection-kill step is the one people forget: an mTLS connection established before revocation stays authenticated until closed. For outbound access to a SaaS, you cannot control the SaaS provider's revocation speed, so enforce at your own egress gateway and additionally revoke the federated credential at the provider.
SRE operational model
- Rotation: SVIDs rotate automatically; intermediates rotate yearly with overlap (new bundle published before the new intermediate issues); the offline root is used only in ceremonies requiring M-of-N key custodians (M of a set of N trusted people must each show up and jointly authorise the root's use, so no single person can act alone).
- Emergency revocation drills: run the partner-revocation play quarterly against a test trust domain and measure the 30-second SLO (service-level objective) end to end.
- Monitoring: SVID expiry histogram (anything within 10 minutes of expiry means rotation is failing), attestation failures by node type, xDS config version skew across proxies, denied-request rates by source trust domain, and bundle freshness per federated domain.
- Audit: every issuance, attestation decision, policy change and allowed/denied cross-domain call is logged with both SPIFFE IDs to an append-only store, so "who could reach what, and when" is answerable later.
Trade-offs and pitfalls
- Federation sprawl: each partner is a trust relationship to review; keep a registry with an owner and expiry date per relationship.
- Sidecar cost: a proxy per pod adds memory and latency; node-level or sidecar-less mesh modes, where one shared agent per node or the workload's own library enforces policy instead of a dedicated proxy container per pod, reduce it but concentrate more identities per enforcement point.
- Break-glass: if the policy control plane is down, proxies keep their last known policy (fail static), which is the right default, but it means revocations cannot land; alert on control-plane health as a security signal, not just an availability one.
You are reviewing the security posture of a shared caching layer (e.g. Redis) and message broker (e.g. Kafka) used by many services on a distributed platform. Walk through the security threats this kind of shared infrastructure is exposed to, and how you would design mitigations at the architecture level for each one you identify.
Sample Answer
Direct answer
A shared cache or message broker is a confused deputy waiting to happen. A confused deputy is a program that has more authority than the party it is currently acting for, and gets tricked into misusing that authority on that party's behalf; here, that means: dozens of services trust the data they read from it, yet any one of those services (or anything that steals its credentials) can usually write to it. The threats that matter are cross-tenant reads, poisoning (injecting or overwriting data other services trust), replay of old messages, denial of service by one noisy client, and plaintext exposure in transit, at rest and in backups. The architecture-level fix is to give every service its own identity with a namespace it alone may write to, authenticate every connection, sign or validate what crosses the shared component, and size the blast radius, how much damage one compromised component or credential can do, by deciding which tenants share a cluster at all.
Why "shared" changes the threat model
A cache inside one service is protected by that service's own boundary. A shared cache or broker is different in two ways:
- Trust is transitive. If the checkout service reads
price:sku-42from Redis and believes it, then anyone who canSET price:sku-42can change prices. The cache has become part of checkout's trusted computing base (the set of components a service implicitly relies on to behave correctly; anything in it can undermine the service's security if it is compromised), but it is administered for everyone. - Credentials are usually coarse. The classic deployment is one Redis password or one Kafka (a distributed log-based message broker: producers append messages to topics, and consumers read them back, usually in place of, or alongside, Redis) principal baked into every service's config. Compromise any one of 80 services and you hold write access to all 80 services' state.
A useful term here: blast radius means how much damage one compromised component or credential can do. Most of the mitigations below are ways of shrinking it.
Threat by threat
| Threat | How it happens on a shared component | Architecture-level mitigation |
|---|---|---|
| Unauthorized read / cross-tenant access | Shared credential; key or topic naming is only a convention; KEYS * or a wildcard consumer subscription reads everyone's data | One principal per service; Redis ACLs restricting each user to a key prefix; Kafka ACLs on prefixed topic names and consumer groups; default deny (Kafka must not run with "allow everyone if no ACL found") |
| Cache poisoning / message injection | A compromised or buggy producer writes a value another service trusts (a price, a session, a feature flag); a rogue producer publishes to a topic a payment consumer processes | Write permission only for the owning service; consumers validate against a schema, using a schema registry (a central service that stores the agreed message format so producers and consumers can check they agree on it) for Avro or Protobuf (compact, schema-based message-encoding formats, as opposed to free-form JSON); for high-value messages, the producer signs the payload so consumers verify origin independent of the broker |
| Replay | An attacker (or an operator resetting consumer offsets, the per-consumer bookmark of how far into a topic it has already read) re-delivers a captured "refund issued" or "grant role" message | Every message carries a unique ID and an issue timestamp; consumers keep a de-duplication store keyed by message ID for at least the replay window and reject messages older than a freshness bound; handlers are idempotent (applying the same message twice has the same effect as once) |
| Topic / key-space hijacking | A service creates a topic or key namespace another team expected to own, or rebinds a consumer group (a named set of consumers that share the work of reading a topic, each partition going to only one member) and steals partitions (the ordered, independently-read shards a Kafka topic is split into) | Topic and consumer-group creation is a platform operation, not a client permission; ACLs on Create, and on consumer groups, not just topics |
| Sensitive data at rest | Session tokens, personal data or access tokens sit in Redis memory, RDB/AOF persistence files (Redis's two on-disk formats: RDB is a periodic full snapshot, AOF is a log of every write, either of which lets Redis survive a restart), Kafka log segments, and backups for their full retention | Do not cache what you cannot afford to leak: cache references or tokenized values; encrypt persistence volumes and backups; for regulated fields, encrypt at the application layer with a per-tenant key so the broker only ever holds ciphertext |
| Eavesdropping in transit | Plaintext Redis protocol or Kafka PLAINTEXT listeners inside a "trusted" network | TLS on every listener, including replication and inter-broker traffic; client certificates or SASL (Simple Authentication and Security Layer, Kafka's pluggable auth framework) for identity |
| Dangerous admin commands | A client can run FLUSHALL, CONFIG SET, DEBUG, or Kafka DeleteTopics / AlterConfigs | Command-level ACLs: application users get data commands only; admin commands belong to a separate, audited operator identity |
| Noisy neighbour / denial of service | Noisy neighbour means one tenant's usage degrades another's, purely by sharing infrastructure, with no compromise needed: one service's hot key, huge values, or runaway producer exhausts memory, network or partitions and takes out every other service | Per-principal quotas (Kafka supports produce/fetch byte-rate and request-rate quotas per user or client ID); maxmemory and eviction policy chosen per cluster; for critical tenants, a separate cluster |
| Stale security state | A revoked service's credential still has a live, authenticated connection | Short-lived credentials, and on revocation actively kill sessions (CLIENT KILL USER, or rotate and drop the principal) instead of waiting for reconnect |
Worked example: prefix isolation you can run
The single highest-value change is replacing the shared password with per-service ACL users. This runs against a stock Redis 7 container:
docker run -d --name cache-demo redis:7-alpine
docker exec -i cache-demo sh <<'EOF'
# ACL SETUSER <name> <rules...>. 'on' enables the user (a new user starts disabled).
# '>orders-pw' sets that password (the '>' means "add this password"; '<' would remove one).
redis-cli ACL SETUSER orders-svc on '>orders-pw' '~orders:*' '+get' '+set' '+del'
redis-cli ACL SETUSER billing-svc on '>billing-pw' '~billing:*' '+get' '+set'
redis-cli --user billing-svc --pass billing-pw --no-auth-warning SET billing:invoice:77 paid
redis-cli --user orders-svc --pass orders-pw --no-auth-warning SET orders:1001 pending
redis-cli --user orders-svc --pass orders-pw --no-auth-warning GET billing:invoice:77
redis-cli --user orders-svc --pass orders-pw --no-auth-warning KEYS '*'
redis-cli --user orders-svc --pass orders-pw --no-auth-warning FLUSHALL
redis-cli --user orders-svc --pass orders-pw --no-auth-warning GET orders:1001
EOF
docker rm -f cache-demo
Output (executed on Redis 7.4):
OK
OK
OK
OK
NOPERM No permissions to access a key
NOPERM User orders-svc has no permissions to run the 'keys' command
NOPERM User orders-svc has no permissions to run the 'flushall' command
pending
Reading it: on turns the new user on (a freshly created ACL user starts disabled, on purpose, so an incomplete definition never silently grants access); >orders-pw sets its password; ~orders:* confines the orders service to its own key prefix, so it cannot read the billing invoice; +get +set +del is a command allow-list, so it cannot enumerate the key space or wipe the database. In production you would also disable the default user, load users from an ACL file managed by your secrets pipeline, and put TLS on the port. (Passwords on the command line are for the demo only.)
The Kafka equivalent, expressed as the actual ACL you would create with Kafka's kafka-acls tool:
kafka-acls --bootstrap-server localhost:9092 --add \
--allow-principal User:orders-svc \
--operation Write --topic orders. --resource-pattern-type prefixed
kafka-acls --bootstrap-server localhost:9092 --add \
--allow-principal User:orders-svc \
--operation Read --group orders-svc-group
This grants orders-svc Write on any topic whose name starts with orders. and Read on its own consumer group, and nothing at all on billing. topics, the same prefix-isolation shape as the Redis ACLs above.
Choosing the isolation level: commit to a tiered model
Namespacing inside one cluster (logical isolation) stops reads and writes across tenants but not resource starvation: one service can still exhaust the shared Redis's memory. So:
- Default: pooled cluster, per-service identity, prefix ACLs, quotas. Right for the long tail of internal services with ordinary data.
- Dedicated cluster for anything whose poisoning or outage is a business incident on its own: sessions, payment events, authorization caches. The cost is more clusters to operate; the gain is that a noisy or compromised neighbour physically cannot touch it.
- Per-tenant encryption keys where customers are the tenants and regulation or contracts require it, so deleting a tenant's key renders their residual data in logs and backups unreadable (sometimes called crypto-shredding).
What would flip the default toward dedicated clusters for everyone: a small number of services with very different trust levels (for example, a third-party-code plugin runner sharing a cache with the payments service). Mixing trust levels in one pool is the real risk, not sharing as such.
Trade-offs and pitfalls
- ACLs without identities are theatre. If all 80 services still load the same ACL user from a shared config file, prefix ACLs isolate nothing. The identity must be per service and delivered by the secrets system.
- Signing everything is expensive and rarely needed. Broker ACLs already stop outsiders; payload signatures earn their cost only where a consumer must not trust other legitimate producers (financial events, authorization changes).
- Replay protection needs state. A de-duplication window of 24 hours at 5,000 messages per second is 5,000 x 86,400 = 432 million IDs; at roughly 16 bytes per ID that is about 6.9 GB before overhead, so bound the window to what an attacker could realistically replay and pair it with a timestamp freshness check.
- Audit the shared component itself. Log authentication failures, ACL denials and admin commands, and alert on them; a burst of
NOPERMfrom one service is either a bug or a compromise. - Common wrong turn: relying on network segmentation ("it is only reachable inside the VPC (Virtual Private Cloud)"). Every service inside that network is exactly the population the threats above come from.
Design a multi-region TLS termination strategy for a global API gateway with three goals: low latency for users, compliance with region-specific key residency rules, and the ability to provide end-to-end encryption when required. Describe where to terminate TLS (edge vs origin), your key management approach, and the SRE operational model for certificate issuance, rotation, and emergency key compromise.
Sample Answer
Direct answer
Terminate TLS at the edge by default, because a handshake with a server 20 ms away instead of 150 ms away is the biggest latency win you can buy. Then carve out two exceptions by hostname: regulated hostnames terminate only on in-region infrastructure, with their private keys held in that region's HSM (hardware security module) or cloud key management service and never exported, and end-to-end hostnames pass through the edge at layer 4 (layer 4 is the raw TCP connection: the edge forwards bytes without looking inside them, as opposed to layer 7, the HTTP level, where it can read and act on the request itself) so TLS terminates at the origin. Everything from edge to origin is re-encrypted with mutual TLS (mTLS, where both sides present certificates). Certificates are issued and rotated automatically via ACME, short-lived, with a rehearsed runbook for key compromise that can replace a certificate everywhere within an hour.
Why edge termination is the default
A new HTTPS connection costs one round trip for the TCP handshake and one for a TLS 1.3 handshake (TLS 1.2 needs two), before the first request byte is sent.
| Where TLS terminates | User RTT (round-trip time) | Setup before first request (TCP + TLS 1.3) |
|---|---|---|
| Origin in another continent | 150 ms | 2 x 150 = 300 ms |
| Edge PoP (point of presence) nearby | 20 ms | 2 x 20 = 40 ms |
The edge then reuses warm, long-lived connections to the origin (connections it keeps open and shares across many users' requests instead of opening and tearing one down per request), so the user never pays the long round trips for setup. Terminating at the edge also lets the edge do its layer 7 jobs: WAF (web application firewall) rules, rate limiting, caching, routing by path.
The cost: at the point of termination, traffic is plaintext in the edge's memory, and the edge holds (or can use) the private key. That is exactly what residency and end-to-end requirements object to.
The three tiers
flowchart LR
U[User] --> E{Edge PoP}
E -- "default: terminate, WAF, re-encrypt mTLS" --> O1[Origin any region]
E -- "regulated host: geo-pinned, in-region PoP only" --> R[In-region edge] --> O2[In-region origin]
R -. "signing request" .-> H[(In-region HSM / KMS key)]
E -- "e2e host: L4 passthrough by SNI" --> O3[Origin terminates TLS]
Tier 1: default (most APIs)
Terminate at the nearest PoP; keys are distributed to the edge fleet encrypted, decrypted only in memory. Re-encrypt to the origin with mTLS using an internal CA (certificate authority, the service that signs certificates), so a network observer between edge and origin sees nothing and the origin rejects anything that is not the edge.
Tier 2: key residency (for example, EU-regulated customers)
Two separate requirements get conflated here; separate them in the interview:
- Key residency: the private key must not leave the region. Solve it with remote signing: the edge performs the handshake, but the one operation that needs the private key (signing the handshake) is sent to a key server inside the region, backed by an HSM. The key never leaves. Several CDNs (content delivery networks) offer this as a product; you can also build it.
- Data residency: the decrypted traffic must not be processed outside the region. Remote signing alone does not satisfy this, because session keys (the symmetric keys the handshake negotiates, and that actually encrypt and decrypt the real traffic afterward) and plaintext still exist at whichever PoP terminated the connection. For that, route the regulated hostnames so termination only happens in-region, restricted to in-region PoPs via geo-DNS (DNS that returns a different answer depending on where the query came from) or anycast (one IP address announced from many PoPs, where the network itself routes each user to the nearest one).
Commit: for regulated hostnames, do both. The latency cost is small for users who are in the region, which is almost everyone the regulation covers; out-of-region users pay the longer round trip, and that is the price of compliance.
Tier 3: end-to-end encryption when required
Options, in order of preference for a strict "the edge must never see plaintext" requirement:
- Layer 4 passthrough: the edge reads only the SNI (server name indication, the hostname sent in clear in the TLS ClientHello, the very first message a client sends to start a TLS handshake, before anything is encrypted) and forwards the encrypted bytes to the origin, which terminates TLS. You lose edge caching, WAF and path routing for that host; move WAF and rate limiting to the origin.
- Application-layer encryption: keep edge termination, but encrypt sensitive payload fields with a key only the origin holds (for example JWE, JSON Web Encryption). The edge still routes by headers. Good for specific fields (card numbers, health data), not whole APIs.
"Re-encrypt at the edge" is not end-to-end, even though it is encrypted everywhere on the wire; say that explicitly.
Key management
- One key pair per hostname group (a set of hostnames that share a certificate and routing tier, for example all default-tier API hosts together) per region; no global wildcard certificate (one certificate valid for every hostname under a pattern like
*.example.com) whose key lives on every PoP (one leak compromises everything). - Regulated keys: generated inside the regional HSM, non-exportable, usable only by the regional key server's identity.
- Default-tier keys: generated centrally, wrapped (encrypted) under a per-region key-encryption key held in KMS, and unwrapped only in PoP memory.
- Internal mTLS between edge and origin uses a separate private CA with short-lived certificates (hours to days), so revocation is rarely needed.
SRE operational model
Issuance and rotation
- Automate with ACME (Automatic Certificate Management Environment, RFC 8555), the protocol public CAs use for automated issuance, and treat any manual step as a defect. The CA/Browser Forum has voted to shrink the maximum public certificate lifetime in steps from 398 days to 47 days between March 2026 and March 2029, so manual renewal will not survive.
- Renew at about two thirds of lifetime; roll out new certificates region by region with a canary PoP; keep the previous certificate deployable for fast rollback.
- Use two public CAs, pre-authorised via DNS CAA records (which list the CAs allowed to issue for your domain), so a CA outage or distrust event does not block renewal.
- Monitor: days-to-expiry for every certificate on every PoP (alert at 14 days, page at 7), handshake failure rate by PoP and by certificate, and Certificate Transparency logs for certificates issued for your domains that you did not request.
Emergency key compromise
A worked timeline, assuming a default-tier key leaked from one PoP:
- 0 to 15 minutes: generate a new key and CSR (certificate signing request); request a new certificate from the backup-ready CA; isolate the PoP.
- 15 to 45 minutes: deploy the new certificate to all PoPs holding that hostname group, canary first.
- After deployment: ask the CA to revoke the old certificate with reason key compromise. Revoke after the new one is live, or you cause an outage. Browser revocation checking is unreliable: most browsers "soft-fail," meaning that if the revocation check itself times out or the network is unreachable, they treat the certificate as valid rather than blocking the page, and many skip the check for performance reasons in the first place. So a revoked certificate often keeps working for whoever already has it cached, and containment really comes from replacement and short lifetimes, not revocation.
- Follow-up: find how the key left, and check whether other keys shared the same exposure path.
For regulated keys in an HSM, "compromise" usually means misuse of the signing service rather than key theft: revoke the offending identity's access to the key server first, then rotate.
Trade-offs and pitfalls
- Treating remote signing as data residency is the most common wrong answer.
- Passthrough everywhere for simplicity throws away the edge's security controls; use it only on hosts that require it.
- Certificate pinning in mobile clients (hard-coding, inside the app, exactly which certificate or public key it will accept, instead of trusting any certificate a public CA has signed) turns routine rotation into an outage; pin to a CA or public key set with a backup, or do not pin.
- What would flip the design: if nearly all traffic were from one region with strict residency, a regional-only termination model (no global edge) becomes simpler and loses little latency.
Design a secure service-to-service authentication and authorization system across multiple clusters and regions. Cover token issuance, short-lived credentials, certificate/key rotation, trust boundaries, least-privilege authorization, fallback when an identity provider is down, and operational practices for key management.
Sample Answer
Direct answer
Give every workload a cryptographic identity issued by its own region's certificate authority after the platform attests what the workload is, and make every credential short-lived so rotation is continuous rather than an event. Authentication happens with mutual TLS (both sides present certificates) using those identities; authorization is a default-deny policy keyed on the caller's identity, evaluated locally. The design principle that answers "what if the identity provider is down" is: verification never needs the issuer online, and issuance is regional with enough credential lifetime left to ride out an outage.
Terms
- Workload identity: a name for a running service, for example the SPIFFE ID
spiffe://prod.eu.example/ns/payments/sa/api(SPIFFE, the Secure Production Identity Framework for Everyone, is an open standard for naming workloads and issuing them certificates, called SVIDs, short for SPIFFE Verifiable Identity Document: the actual certificate or token a workload presents). - Attestation: proving what a workload is from facts the platform controls (its Kubernetes service account, node, image) rather than from something the workload says.
- Certificate chain: a certificate is signed by the authority above it (its issuer); that issuer's own certificate is signed by the authority above it, and so on up to a root. A workload's certificate, at the bottom of that chain, is called a leaf certificate. To trust a leaf, a verifier walks the chain up to a root it already has in its trust bundle; if it has never been told about one of the intermediates in between, it cannot validate anything that intermediate signed, no matter how correctly the leaf itself was issued. That is what "chained to a CA" means, and it is why the rotation order below matters.
- Trust domain / trust boundary: the set of workloads that share a root of trust; crossing a boundary requires explicit federation: the two sides must each deliberately configure trust for specific identities in the other domain, rather than automatically trusting everything the other domain issues.
- Trust bundle: the set of CA (certificate authority) certificates a verifier accepts.
Architecture
flowchart TB
ROOT[Offline root CA] --> ICA1[Intermediate CA: region EU]
ROOT --> ICA2[Intermediate CA: region US]
ICA1 --> AG1[Node agents EU clusters]
ICA2 --> AG2[Node agents US clusters]
AG1 --> W1[Workload certs, 24h]
AG2 --> W2[Workload certs, 24h]
PAP[Policy repo] --> PE1[Local policy check EU]
PAP --> PE2[Local policy check US]
Token and credential issuance
- A node agent (a small process the platform runs on every host, distinct from the application itself) attests the workload using facts it can verify locally: its Kubernetes service account, its namespace (a Kubernetes grouping boundary that scopes and isolates related workloads), and its image digest (a cryptographic hash identifying the exact container image build, so a tampered or swapped image gets a different identity). It then requests a certificate from the regional intermediate CA.
- The workload receives an X.509 certificate (X.509 is the standard format essentially every TLS certificate uses) valid for 24 hours, with its SPIFFE ID in the subject alternative name, a field in the certificate that states which identity or identities it is valid for.
- For calls that also need application-level claims (user on whose behalf, audience), a regional token service issues JWTs (JSON Web Tokens: compact, signed tokens that carry a set of claims, such as who the caller is and who the token is for) valid for 5 minutes with an audience claim, signed with a key whose public half is distributed alongside the trust bundle.
Short-lived credentials and outage tolerance, with numbers
Agents renew a certificate when 50% of its lifetime remains. The worst case for an issuer outage is that it starts at the moment renewal was due, so the tolerance is the remaining lifetime at the renewal point:
| Certificate lifetime | Renew at | Issuer outage the fleet survives | Useful life of a stolen cert |
|---|---|---|---|
| 1 hour | 30 min left | 30 minutes | up to 1 hour |
| 24 hours | 12 h left | 12 hours | up to 24 hours |
| 7 days | 3.5 days left | 3.5 days | up to 7 days |
Commit to 24 hours for certificates. Twelve hours of outage tolerance is long enough to fix a broken CA across a night, and a day is a tolerable exposure window when combined with the other controls. Load is trivial: 50,000 workloads renewing every 12 hours is 50,000 / 43,200 s, about 1.2 issuances per second. The JWT layer stays at 5 minutes because it carries user context, where faster expiry matters more.
Certificate and key rotation
- Leaf certificates rotate continuously by construction.
- Intermediate CAs rotate every few months by overlap: add the new intermediate to trust bundles first, wait until every verifier has the updated bundle, then start issuing from it, and remove the old one only after the last certificate it signed has expired (24 hours plus margin). Reversing that order causes an outage because verifiers reject certificates chained to a CA they have not been told about.
- The root is kept offline in an HSM (hardware security module) and used only to sign intermediates. Root rotation is the same add-then-switch-then-remove sequence stretched over weeks.
- JWT signing keys rotate the same way, publishing the new public key before signing with it.
Trust boundaries
- One trust domain per region (or per environment), not one global domain. A compromised EU intermediate lets an attacker mint EU identities only.
- Cross-region calls use federation: US verifiers are explicitly configured to accept the EU bundle, for specific identities. The US payments service accepts
spiffe://prod.eu.example/ns/checkout/sa/apiand nothing else from EU. - Staging and production never share a root. This is the most common real-world boundary failure.
Least-privilege authorization
Policies are written against identities, not network addresses: "payments accepts calls from checkout on POST /charges; everything else is denied". Policies live in version control, are compiled and pushed to a local enforcement point in every workload (a sidecar, a helper process deployed alongside the application container, or a library linked into the application itself), and are evaluated without a network call. New services start with zero inbound permissions and receive them through review. Every allow and deny is logged with caller identity and policy version.
Fallback when the identity provider is down
Walk through what depends on the issuer:
| Function | Depends on issuer? | Behaviour during outage |
|---|---|---|
| Verifying an existing certificate or token | No: needs only the cached trust bundle and public keys | Unaffected |
| Renewing a workload certificate | Yes | Existing certs keep working for up to 12 hours; alert immediately |
| New pods starting (a pod is the smallest deployable unit in Kubernetes, one or more containers scheduled together on a host) | Yes | Scale-ups and deploys stall; freeze deploys in that region |
| Minting 5-minute JWTs | Yes, regional token service | Fail over to a second token-service instance in the same region; cross-region failover only if the other region's issuer is in the verifiers' federated bundle |
What we deliberately do not do: fall back to a long-lived static shared secret, or disable mTLS. Both turn an availability incident into a security incident. If the outage outlasts the 12-hour tolerance, the controlled response is an emergency extension: issue longer-lived certificates from a standby intermediate held for this purpose, logged and time-boxed.
Operational practices for key management
- Private keys for leaf certs are generated on the host and never leave it; only CSRs (certificate signing requests) travel.
- CA keys live in HSMs or a cloud KMS (key management service); root operations require two people (dual control) and are recorded.
- Monitor: certificate time-to-expiry across the fleet (alert when any workload is under 25% of lifetime), issuance error rates, and bundle version per verifier.
- Rehearse a compromised intermediate: revoke it by removing it from bundles, re-issue the region's workloads from a fresh intermediate, and measure how long that takes. That measured time is your real recovery objective.
Trade-offs and pitfalls
- Short lifetimes replace revocation lists. Certificate revocation lists and OCSP (Online Certificate Status Protocol) checks are rarely reliable at mesh scale (the scale of a service mesh, the network of many services all calling each other over these short-lived certificates); expiry is. The cost is issuer availability, which the table above quantifies.
- Regional isolation costs federation configuration. It is worth it: it bounds a CA compromise to one region.
- Clock skew breaks short-lived credentials. Allow a small skew tolerance (a minute or two) and monitor time sync.
- Common wrong turn: authorizing by IP or namespace label. Those change with every reschedule; identities do not.
Why are tamper-evident and append-only audit logs important for a distributed system? As an SRE, describe how you would design audit logging across services and regions so logs can't be silently altered or deleted, how you'd keep events correctly ordered per entity, and what operational controls you would put in place to preserve logs during an incident.
Sample Answer
Direct answer
Audit logs are the record you use to answer "who did what, when" after something goes wrong. That makes them the first thing an attacker (or a panicking insider) wants to edit. Append-only means records can be added but never changed; tamper-evident means that if someone does change or delete a record, verification detects it. Across services and regions, the design is: each service writes events with a per-entity sequence number to a replicated log (an ordered sequence of events copied to multiple machines, so no single machine failure loses it and no single operator holds the only copy), events are chained by hashes, the chain head is regularly anchored somewhere the writers cannot modify, and the storage itself enforces retention that no operator can override.
Why it matters in a distributed system
- Forensics: after a breach you must reconstruct what happened across many services. If the log can be silently edited, you cannot trust any conclusion.
- Compliance: standards such as PCI DSS (Payment Card Industry Data Security Standard) and SOC 2 (a widely used security-controls audit report) require audit trails protected from modification.
- Distributed makes it harder: many writers, many regions, clocks that disagree, and many operators with access to some part of the pipeline.
Design
1. Making changes detectable: hash chains
Each record stores the hash of the previous record, so changing any record changes every hash after it. This short program shows what a chain catches and what it does not:
import hashlib, json
GENESIS = "0" * 64 # placeholder "previous hash" used only for the first entry, which has no real predecessor
def entry_hash(prev_hash, event):
body = json.dumps(event, sort_keys=True, separators=(",", ":"))
return hashlib.sha256((prev_hash + body).encode()).hexdigest()
def append(log, event):
prev = log[-1]["hash"] if log else GENESIS
log.append({"event": event, "prev": prev, "hash": entry_hash(prev, event)})
def verify(log):
prev = GENESIS
for i, rec in enumerate(log):
if rec["prev"] != prev or rec["hash"] != entry_hash(prev, rec["event"]):
return f"broken at entry {i}"
prev = rec["hash"]
return "ok"
log = []
append(log, {"seq": 1, "actor": "alice", "action": "role.grant", "target": "bob:admin"})
append(log, {"seq": 2, "actor": "bob", "action": "export", "target": "customers.csv"})
append(log, {"seq": 3, "actor": "alice", "action": "role.revoke", "target": "bob:admin"})
head = log[-1]["hash"]
print("clean log:", verify(log), "| head", head[:12])
# 1) edit an event in place
log[1]["event"]["target"] = "nothing.csv"
print("after edit:", verify(log))
log[1]["event"]["target"] = "customers.csv"
# 2) delete the incriminating entry and re-link the next one
forged = [log[0], dict(log[2], prev=log[0]["hash"])]
print("after delete + relink:", verify(forged))
# 3) delete it and recompute every later hash: the chain verifies again...
rebuilt = []
for rec in (log[0], log[2]):
append(rebuilt, rec["event"])
print("after full rewrite:", verify(rebuilt), "| head", rebuilt[-1]["hash"][:12])
# ...which is why the head hash must be anchored somewhere the writer cannot change
print("head matches anchored copy:", rebuilt[-1]["hash"] == head)
clean log: ok | head 1537d687119a
after edit: broken at entry 1
after delete + relink: broken at entry 1
after full rewrite: ok | head 8cf5f38f565b
head matches anchored copy: False
The third case is the important lesson: a hash chain alone does not stop someone with write access from rebuilding it. It only becomes tamper-evident when the head hash is anchored: copied every few minutes to a place the log writers cannot change (a separate account's locked storage, another organization, or a public transparency log, a shared append-only log, such as the one behind Certificate Transparency, that many independent parties can audit). This is a scale optimisation, not a change to the core idea above: instead of anchoring every single event's hash (expensive at high volume), events are batched into a Merkle tree (a tree of hashes whose single root commits to every event in the batch) and only that one root is anchored. A Merkle proof then shows a specific event was in a batch using about log2(n) hashes, which is 20 hashes for a batch of one million events, instead of handing over all one million. If your system's volume is low enough to anchor every event's hash directly, you do not need this step at all.
2. Making changes impossible at the storage layer
- Write to storage that enforces immutability, for example Amazon S3 Object Lock in compliance mode, where no user, including the account root, can delete or overwrite an object before its retention date.
- Put the audit store in a separate account owned by the security team, so compromising a production account does not grant delete rights.
- Replicate to a second region so losing one region does not lose the trail.
3. Keeping events ordered per entity
Wall clocks across servers drift by milliseconds or more, so timestamps alone cannot order two events that happened close together. Instead:
- Route all events for one entity (a user, an account, a resource) through one partition of the log. A partition is one independently ordered slice of the log; the log is split into many partitions (think separate lanes) so it can scale, and every event for a given entity ID is routed, by that ID, to the same lane every time. Within a partition, order is total: every event in that one lane has an unambiguous before-and-after position. Order is not directly comparable across two different partitions.
- The writer for that entity assigns a monotonic sequence number (1, 2, 3...). A gap reveals a missing event; a duplicate reveals a replay.
- Keep the wall-clock timestamp for humans, but use the sequence number for ordering. For causality across entities, a hybrid logical clock (a timestamp that never goes backwards and carries causal order: if event X caused event Y, Y's clock value is guaranteed to be greater than X's, so a reader can tell cause from effect even when the events sit in different partitions) is the usual addition.
In the example, seq 1, 2, 3 show that Bob's export happened while he had admin, even if the servers' clocks disagreed.
4. Operational controls during an incident
- Separation of duties: the people responding to an incident cannot delete or modify audit data; audit-store admin is a different role with its own approval.
- Legal hold: a flag placed on specific records that overrides the normal retention schedule, so declaring an incident and applying a hold guarantees those logs cannot be deleted, even by an expiry job, until the hold is explicitly lifted.
- Protect the pipeline, not just the store: agents buffer locally if the pipeline is down, and a gap in a service's sequence numbers raises an alert. An attacker's first move is often to stop logging, so "a service stopped sending audit events" is itself a high-priority alert.
- Continuous verification: a job re-verifies chains against anchored heads and pages on any mismatch.
Trade-offs and pitfalls
- Immutability versus data-protection deletion rights: you cannot delete a person's data from a compliance-locked log. Log IDs rather than personal data, or encrypt personal fields with a per-person key you can destroy.
- Per-entity ordering is cheap; global ordering is expensive. A single global sequence becomes a throughput bottleneck. Order per entity and accept that cross-entity order is approximate.
- Common wrong turn: relying on "only admins can delete". The threat model includes admins.
Unlock Full Question Bank
Get access to all 12 Distributed Systems Security and Trust interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.