Flipping default_policy to deny on a Live Datacenter
Contoso is turning on ACLs for real. Servers already run with acl.enabled = true and default_policy = "allow" while the team prepares tokens. Production has three servers, 25 client agents, Connect with Envoy sidecars on 8 services, feature flags in KV under config/, and a prepared query the edge tier calls. The plan: write tokens into agent configs, then flip default_policy to deny on the servers and restart them one at a time. The flip happens at 02:00. Within a minute, applications that resolve redis.service.consul get NXDOMAIN, newly started services never appear in the catalog, and the checkout service's KV reads return 403. Server logs show Permission denied on catalog RPCs. No token was ever configured on any client agent.
1. Three different symptoms appear simultaneously. What is the common root cause, and what is the fastest way to confirm it before changing anything?
Read the worked answer
All three symptoms are the same failure seen through three interfaces: every request that no longer carries a privileged token now falls back to the anonymous token, because the client agents were never given one and the flip made the anonymous token powerless.
- DNS NXDOMAIN: DNS lookups are ACL-checked. The querying agent applies its default token, or the anonymous token if it has none. With default-deny and no rights on that token, the lookup returns no services - which callers see as NXDOMAIN, not a 403.
- Services not appearing: the agent's anti-entropy sync needs write access to its node and its services. 403 on that RPC is what the server logs show.
- KV 403: the checkout application is reading KV with no token (or an agent default token that lacks
key:readonconfig/).
Confirmation, cheapest first: run consul catalog services -token=<management token> and compare to the same command without a token; read a client agent's log for the exact Permission denied line and the AccessorID it names; and use consul acl token read -self -token=<the token the app is using> to dump the effective policies. That tells you whether the token is missing entirely or merely under-scoped - a distinction that changes the fix.
2. What token shape should each client agent have, and what should the anonymous token look like in a deny-by-default cluster that serves DNS and a prepared query?
Read the worked answer
Agent tokens: the idiomatic shape is one token per node built from node identity, e.g. consul acl token create -node-identity=web-01:dc1. That scopes the token to node web-01 in dc1, which is what the anti-entropy sync needs; services registered on that node are covered by the same identity. Where a team needs tighter blast radius (for example a single agent hosting services owned by different teams), create per-service identities (-service-identity=payments) and attach them explicitly, and remember the sidecar proxy is its own service - an Envoy sidecar for payments needs write access to payments-sidecar-proxy, which is a separate identity from payments.
Anonymous token: in a default-deny cluster that still answers DNS and serves a prepared query, the anonymous token must be given read-only discovery rights - service:read and node:read on the relevant prefixes, plus query:read on the prepared query name. Without those, every unauthenticated DNS lookup becomes NXDOMAIN, which is what broke first here.
Client-side default token: if you would rather not open the anonymous token, set acl.tokens.default on client agents so DNS lookups carry a read-only token. This is the tighter option and is preferred when the discovery surface shouldn't be world-readable.
3. Describe the safe order of operations and the lockout guards the team should have used, given they were flipping a live cluster from allow to deny.
Read the worked answer
The mistake was restarting servers into default-deny before any client had credentials. A workable sequence:
- Secure a management credential first. Run
consul acl bootstrap(or setacl.tokens.initial_managementfor the first boot only) and store the SecretID outside the cluster. Every subsequent token creation depends on it, and there is no recovery path if it is lost and no other management-capable token exists. - Create all tokens while default_policy is still allow. Nothing is blocked yet, so this is a no-op for traffic and lets you validate token scope with
consul acl token read -selffrom each host. - Roll client agents first, one node per batch, writing
acl.tokens.agent(andacl.tokens.defaultwhere needed) into config and restarting the agent. Verify each node withconsul members -token=<token>and by confirming a service registration round-trips. - Roll servers last, one at a time, checking
consul operator raft list-peersand waiting for a stable leader between nodes. The policy flip only takes effect when the servers reload. - Verify the read paths explicitly: a DNS lookup from an agent, the prepared query, a KV read, and one Connect call through a sidecar - each fails under a different ACL rule and a green catalog does not prove any of them work.
Note that unsealing a lockout after the fact is painful: if no management token exists and acl.tokens.initial_management was never set, you are editing server config files in place, which is why step 1 is non-negotiable.