CMD Guide
HomeSystem DesignMicroservices Patterns

Security Considerations

Service-registry security works by making every write (register/deregister) and every read (query) pass an identity check the registry itself enforces: the caller presents a credential — an ACL token or a client TLS certificate — and the registry compares the service name being touched against a policy bound to that credential before it will mutate the routing table or return the map of who lives where. If that gate is missing or misconfigured, discovery degrades from "the place services find each other" into "the place an attacker reroutes your traffic."

Why the registry is the sensitive part

A registry is a live, authoritative index of every instance's address and health. Two properties make it a prize target. First, it is write-authoritative: whatever it says is where payments lives becomes where clients send card numbers. Second, it is a topology map: a single unauthenticated read enumerates every internal service, IP, port, and health state — reconnaissance an attacker would otherwise spend weeks assembling. Encrypting the channel (TLS) hides the traffic from eavesdroppers but does nothing to stop a caller who is allowed to talk to the registry from lying to it. Encryption and authorization are orthogonal; you need both.

Trace: a service-spoofing attack, with and without an ACL gate

A pod is compromised on the cluster and can reach the Consul HTTP API on :8500. It wants traffic destined for payments. The real payments instance is at 10.0.2.10:8443; the attacker's listener is at 10.0.9.99:8443. Follow the same request through two configurations.

StepRegistry with default_policy = allow (or no ACLs)Registry with default_policy = deny + per-service token
1. Attacker sends
PUT /v1/agent/service/register
{"Name":"payments","Address":"10.0.9.99","Port":8443,"Check":{"TTL":"10s"}}
Request carries no token (or the over-privileged anonymous token). Nothing to check against.Request must carry X-Consul-Token. Attacker only has the anonymous token, which has no service "payments" write rule.
2. ACL evaluationSkipped / passes — writes are permitted by default.Consul looks up the token's policy, finds no write on service name payments. 403 Permission denied.
3. Registry stateNow two healthy payments instances: 10.0.2.10 (real) and 10.0.9.99 (evil).Unchanged — one instance, 10.0.2.10.
4. A client resolves payments.service.consulConsul returns both A-records; round-robin sends ~50% of requests to 10.0.9.99.Returns only 10.0.2.10.
5. OutcomeHalf of all card numbers are POSTed to the attacker's listener. Silent, no error, no crash.Attack fails at step 2; audit log records a denied write from an unexpected token/IP.

What the ACL policy actually gates

The gate is not "is this caller authenticated" — it is "is this caller allowed to write this exact service name." A Consul ACL policy is explicit rules over named resources. The policy issued to the real payments deployment says: you may register/deregister payments, and you may read every service (so it can discover its own dependencies), and nothing else.

# policy: payments-svc.hcl
service "payments" {
  policy = "write"      # register / deregister ONLY this name
}
service_prefix "" {
  policy = "read"       # may discover any service (least-priv variant below)
}
node_prefix "" {
  policy = "read"
}

You bind that policy to a token and hand the token only to the payments workload (a Kubernetes Secret or a Vault-issued lease), never checked into an image:

consul acl policy create -name payments-svc -rules @payments-svc.hcl
consul acl token create -description "payments" -policy-name payments-svc
# -> SecretID: 8f4e...  (mounted at /var/run/secrets/consul-token)

The attacker cannot forge this: the SecretID is a 128-bit random value the registry stores server-side and matches on every request. Absent that exact token, the service "payments" { write } rule never fires, so step 2 in the trace returns 403. This is the whole mechanism — a name-scoped write grant that only the legitimate owner of that name holds.

diagram
diagram

Least privilege, sharpened

The service_prefix "" { read } rule above still lets any authenticated service read the whole map. Tighten it: a frontend that only calls accounts and orders gets a policy that names exactly those, so a compromised frontend token still cannot enumerate payments or the internal fraud-scoring service. This turns one breach into a bounded blast radius instead of a full topology dump.

# policy: frontend-svc.hcl — read only its two dependencies
service "accounts" { policy = "read" }
service "orders"   { policy = "read" }
service "frontend" { policy = "write" }   # register itself

Pitfalls

When to use which control (and what it costs)

Registry security is layered; the real decision is where your identity lives.

ControlWhat it gatesChoose when…Cost / limits
Bearer-token ACLs (Consul ACLs)Register/query authorization, name-scopedSingle control plane; you need per-service registration authz and read segmentation; team can rotate tokens.Tokens are replayable secrets; needs rotation infra; identity ≠ the running workload, just whoever holds the string.
mTLS / mesh identity (Consul Connect, Istio+SPIFFE/SPIRE)Per-call workload identity — the cert IS the identityZero-trust between services; you want identity that can't be replayed and is cryptographically tied to the workload; short-lived certs.Sidecar per pod → ~0.5–2 ms added latency + memory; cert-rotation and CA operations; real operational complexity.
Network isolation only (private subnet, NetworkPolicy)Who can reach the registry / each pod at L3/L4Baseline hardening, always-on layer; keeps the registry off the public internet.Flat trust inside the perimeter — one compromised pod defeats it entirely. A layer, never the whole answer.

Choose Consul ACLs when your threat is fake registrations and topology leaks and you can manage tokens. Prefer a service mesh (mTLS/SPIFFE) when a leaked bearer token is unacceptable and you need cryptographic, short-lived, per-call workload identity — accepting the sidecar latency and operational tax. Never rely on network isolation alone: use it under one of the other two, because internal-flat-trust dies at the first compromised pod. And on capacity: put rate limits on the registry's write/query API (it is a shared chokepoint — a registration flood can starve real lookups) and turn on audit logging, because in the deny-column trace it is the audit line that tells you an attack happened at all.

Microservices threat model: three attack paths

Registry spoofing is one path; a complete microservices threat model also covers service-to-service traffic and secret handling. The table below names the attacker, the vulnerable surface, and the control.

Attack pathWhat the attacker wantsWhere it livesPrimary control
Service impersonationCall another service as if they were a legitimate workload.Service-to-service call over the internal network.mTLS with workload identity (SPIFFE/SPIRE, Istio, Consul Connect) — the cert IS the identity and cannot be replayed.
Token propagation / replaySteal a bearer token or user JWT and reuse it against downstream services.Logs, env dumps, network captures, compromised sidecar.Short-lived tokens, scoped claims, automated rotation, and audit of token use.
Registry spoofingRegister a malicious instance so clients route traffic to it.Service-discovery write path.Per-service ACLs or mTLS identity bound to registration; default_policy = deny.
Secret leakExtract database passwords or API keys from images, config, or backups.Git history, container layers, runtime memory, backup files.External secret store, injection at runtime, short TTLs, and rotation on suspicion.
Sidecar bypassTalk directly to a peer pod, skipping the proxy's mTLS/policy.Pod network inside the cluster.NetworkPolicy / strict mTLS enforcement that rejects plaintext intra-cluster traffic.

Sidecar vs library: where to put the policy

You can implement mTLS, retries, and telemetry in an application library or in a sidecar proxy. The choice is an ownership trade-off.

DimensionLibrary (e.g. resilience4j, Istio SDK)Sidecar proxy (e.g. Envoy, Linkerd)
Language coverageOne language per library; polyglot teams need N implementations.Language-agnostic; works for any containerized workload.
Upgrade velocityRequires rebuilding and redeploying every service.Roll out proxy updates independently of application code.
Operational visibilityMetrics depend on each library emitting the same shape.Uniform metrics/logs across the fleet from the proxy.
Latency / resource costIn-process, no extra hop.Extra hop (localhost TCP or Unix socket) and per-pod memory.
DebuggingOne process, one stack trace.Must correlate app logs with proxy logs; certificates can expire independently.
When to preferSingle-language platform with strong standardization.Polyglot services or when security policy must be enforced uniformly.

Zero-trust service mesh checklist

A mesh is not zero trust just because it uses mTLS. Use this checklist during design review.

Secret-leak trace and rotation story

The following trace follows a real-shaped incident: a database password is accidentally logged, detected, and rotated without downtime.

TimeEventAction
T+0A developer adds debug logging to the payments service to trace a connection issue; the log line includes the DB_PASSWORD environment variable.Code review misses it; the change deploys.
T+2 hCentralized log scanning flags a high-entropy string matching the secret-vault format in application logs.Secret-store audit log confirms the value corresponds to payments/db-password.
T+5 hIncident declared. Access logs for the log system are checked: no evidence the log line was queried by unauthorized principals, but rotation proceeds because exposure cannot be ruled out.Security team triggers emergency rotation.
T+6 hThe secret store (Vault/AWS Secrets Manager) generates a new password and updates the active version. The old version is marked deprecated with a 4-hour grace period.Running pods continue using the old credential during the grace window.
T+7 hA config-change event is pushed to all payments pods; each pod reloads its connection pool with the new credential without restarting the process.No deploy is needed; the rotation is transparent.
T+10 hGrace period expires; the old password is revoked in the database. Any pod that failed to rotate fails health checks and is replaced.Audit confirms 100% of pods are on the new credential.

Lessons from the trace: (1) secrets should never be interpolated into log statements — use structured logging and mask known secret patterns; (2) rotation must be routine and fast, not a multi-day migration; (3) a grace period prevents a hard cutover from breaking in-flight connections; (4) the ability to reload without redeploy makes rotation cheap enough to do often.

Rate-limiting and auditing the registry write path

ACLs decide who may register; two operational controls bound how hard the write path can be hit and make abuse visible after the fact. First, a Consul agent's limits block caps how many concurrent HTTP connections a single client may hold open against it (default 200):

# agent configuration — cap concurrent HTTP connections per client
limits {
  http_max_conns_per_client = 100
}

Request-rate limiting is a separate, control-plane concern: Consul throttles read and write requests against the servers through its control-plane-request-limit configuration entry (with permissive and enforcing modes) — there is no per-client-IP rate-limit knob in the agent config, so if you need true per-caller throttling of the registry's HTTP API, put a rate-limiting proxy or load balancer in front of it (see the rate-limiter page below). For visibility, Consul's audit logging — an Enterprise feature — records every HTTP API request, including denied registration writes, as a JSON event; alert on denials against service-registration endpoints. Check the exact configuration shapes against the current Consul documentation (agent limits reference, the control-plane-request-limit config entry, and Enterprise audit logging) — these surfaces have shifted across versions.

Takeaways

Related pages


Sources: HashiCorp Consul documentation — ACL system, security model, and Connect service mesh; the SPIFFE/SPIRE specification and Istio security architecture for workload identity via mTLS; Chris Richardson, Microservices Patterns (Ch. 11, security); Sam Newman, Building Microservices, 2nd ed. (zero-trust and defence-in-depth); Kubernetes docs on RBAC and NetworkPolicy; OWASP guidance on service-to-service authentication. Re-authored/Deepened for this guide.

🤖 Don't fully get this? Learn it with Claude

Stuck on Security Considerations? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Security Considerations** (System Design) and want to truly understand it. Explain Security Considerations from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Security Considerations** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Security Considerations** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Security Considerations** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes