In Consul's service mesh, what is an intention, what identity does it match on, and what happens to a call between two mesh services when no intention matches it?
answer
- authorization between two named services
- identity, not source IP address
- read from the mTLS certificate's SPIFFE SAN
- checked by the destination's sidecar
- no match falls back to ACL default_policy
basics
~20 sA Consul intention is an authorization rule that permits or denies traffic from one mesh service to another. It matches the service identity carried in the sidecar's mTLS certificate rather than a source IP address. When nothing matches, Consul falls back to the ACL default_policy.
solid answer
~50 sAn intention is Consul's service-to-service authorization rule, written as a `service-intentions` config entry whose `Name` is the destination service and whose `Sources` list callers with `Action = "allow"` or `"deny"`. The decision is made on **identity**, not address: every sidecar holds a leaf certificate whose URI SAN is a SPIFFE ID naming the service, so when `web`'s proxy opens an mTLS connection to `db`'s proxy, the destination proxy reads the caller's service name out of that certificate and applies the matching intention. Enforcement happens at the destination sidecar, using the intention set Consul has already pushed to it, so changes take effect in seconds with no restart. If no intention matches the pair, Consul defers to the ACL `default_policy`: `deny` gives you a deny-by-default mesh, `allow` lets everything through. Teams that want default deny either run ACLs with `default_policy = "deny"` or write an explicit `*` → `*` deny intention.
code
hcl · 13 linesKind = "service-intentions"
Name = "db"
Sources = [
{
Name = "web"
Action = "allow"
},
{
Name = "*"
Action = "deny"
}
]go deeper
Be ready to say plainly that an intention allows or denies one service calling another, and that the caller is identified by the certificate its sidecar presents rather than by an IP address.
Explain the config entry shape — destination in Name, callers in Sources — how the SPIFFE identity is read from the peer certificate, and that the default when nothing matches follows the ACL default_policy.
Show that you have operated this: precedence rules over overlapping intentions, enforcement living on the destination proxy so changes apply without restarts, and the gap where traffic reaching the app port directly is never governed at all.
Own the posture question. Argue for ACLs with default_policy = "deny" from day one, describe how you would migrate an allow-all mesh to default deny without an outage, and say who authors intentions as services multiply.
## What an intention actually is In Consul's service mesh, an **intention** is an authorization rule between two *services* — not two hosts, not two IP ranges. It is expressed as a config entry: ```hcl Kind = "service-intentions" Name = "db" # the DESTINATION service Sources = [ { Name = "web", Action = "allow" }, { Name = "*", Action = "deny" } ] ``` You write it with `consul config write intentions.hcl`, or with the older `consul intention create -allow web db` CLI. The entry is named after the destination, and every caller you care about appears in `Sources`. ## Where the identity comes from When a service joins the mesh, Consul issues its sidecar proxy a short-lived X.509 leaf certificate signed by the mesh CA. The certificate's URI Subject Alternative Name is a SPIFFE-style identifier of the form: ``` spiffe://<trust-domain>.consul/ns/<namespace>/dc/<datacenter>/svc/web ``` Both sides present a certificate — the connection between two sidecars is always mutual TLS. So when `web`'s sidecar dials `db`'s sidecar, the receiving proxy does not have to guess who is calling from a source address that may belong to a NAT gateway, a shared node, or a recycled pod IP. It reads `svc/web` out of the verified peer certificate. That is the entire reason intentions are more useful than a firewall rule in a dynamic environment: the identity travels with the workload, and it survives rescheduling, autoscaling and IP reuse. ## Where enforcement happens Enforcement is on the **destination** side. The inbound listener of the destination's sidecar terminates the mTLS connection and checks the caller's identity against the intentions it holds. Consul distributes the relevant intention set to each proxy in advance, so the check is a local decision — there is no per-request round trip to a Consul server. Two practical consequences follow. First, adding or removing an intention takes effect within seconds and requires no restart of the application or the proxy. Second, a proxy that has temporarily lost contact with the Consul servers keeps enforcing the last set it received rather than failing open. ## The default when nothing matches This is the part candidates most often get wrong. Consul does not have a fixed built-in default. If no intention matches a source/destination pair, the result follows the ACL system's `default_policy`: - `default_policy = "deny"` — the mesh is deny-by-default; only explicitly allowed pairs connect. This is the production posture. - `default_policy = "allow"` — which is what you get in a quick dev setup with ACLs disabled — everything is permitted unless an intention denies it. If you cannot yet turn on strict ACLs, you can still get default-deny semantics by writing a catch-all `*` source with `Action = "deny"` and layering specific allows above it. ## Precedence, not "deny always wins" When several intentions could apply, Consul resolves them by **precedence**, computed from how specific the source and destination names are. An exact service name outranks a wildcard, and a more specific match wins regardless of whether it says allow or deny. So an explicit `web → db` allow beats a `* → db` deny; deny does not automatically override. Candidates who assume "the deny rule always wins" are surprised when a broad deny fails to close a hole an exact allow left open. ## L4 by default, L7 when the protocol says so By default an intention is a connection-level (L4) decision: the whole TCP connection is either accepted or refused, and a refused caller sees the connection closed rather than an application error. If the destination's protocol has been declared as HTTP-based, an intention can instead carry `Permissions` with per-request rules (path, method, header), and a request that fails them gets an HTTP 403 while the connection itself stays up. ## What intentions do not do They do not encrypt anything — the mTLS between sidecars does that, and it is on regardless of whether the call is allowed. They also govern only traffic that actually enters a sidecar: anything that reaches the application's own listening port directly bypasses them entirely, which is why mesh workloads should bind their app port to localhost or be protected at the network layer as well.
- If both a `web` → `db` allow and a `*` → `db` deny exist, which one applies?The allow. Consul resolves overlapping intentions by precedence computed from how specific the source and destination names are, and an exact name outranks a wildcard. Deny does not win by virtue of being a deny — it only wins when its match is at least as specific. That is why a broad catch-all deny is a floor, not an override.
- A service inside the mesh is still reachable from a machine that has no sidecar. Why did the intention not stop it?Intentions are enforced by the sidecar's inbound listener, so they only govern traffic that arrives there. A client that connects to the application's own port directly never meets the proxy and is never asked for a certificate. The fix is to bind the application to localhost (or an interface only the proxy can reach) so the sidecar is the sole ingress path.
- How quickly does deleting an intention take effect on running traffic?Within seconds, and without restarting the application or the proxy. Consul pushes the intention set to each sidecar ahead of time, so the update is a config push followed by a local decision change. Existing connections that were already allowed at L4 are not necessarily torn down, so a deny is best thought of as blocking new connections rather than instantly severing established ones.
A firewall rule is a guest list of street addresses; an intention is a guest list of names, checked against photo ID at the destination's door.
saying these in an interview costs you the question
- Says intentions match on the caller's source IP address
- Assumes a deny intention always overrides a matching allow
- Thinks intentions are what encrypts the traffic
- Believes a missing intention always means the call is denied
- Claims an intention change requires restarting the proxies