Guides / Ferrum Anvil / Auth and TLS

Auth and TLS

Anvil applies authentication to the final bytes of every send and keeps TLS verification on unless you scope a bypass to a profile.

Ferrum Anvil v0.1.1 is an unsigned preview. Verify downloads with SHA256SUMS. macOS installers are not Developer ID signed or notarized and Windows installers have no code-signing certificate. In-app updates use verified minisign signatures; this does not code-sign the installers. See the preview downloads and Anvil overview.

Supported Auth Types

Auth profiles can be set on a workspace, a folder or a request, and are inherited downwards.

TypeBehaviour
API keyHeader or query parameter. A key in the query string triggers a warning, and its value is redacted in records.
Basic, BearerAdded to the prepared request on each send.
JWTSigned locally with HS, RS or ES algorithms; time claims are regenerated per send.
OAuth 2.0Client credentials, refresh, and authorization code with PKCE in the system browser.
Ferrum HMAC v2Signed over the final bytes with a fresh nonce per send. Legacy v1 is an explicit, off-by-default opt-in.
DPoPA new proof for every send.
WS-SecurityUsernameToken and user-supplied SAML.
JWT-SVIDA SPIFFE JWT-SVID as a bearer token, from a SPIFFE Workload API, a file or a variable, checked locally before sending. See SPIFFE Workload API.
Multi-authSeveral mechanisms on one request.

Session protocols

WebSocket, gRPC and SSE sessions apply auth as headers or query parameters. Some combinations are refused before any traffic: an auth type that rewrites the body (WS-Security) on a session, query-parameter auth on gRPC, and any auth profile on raw TCP or UDP, where client certificates come from the TLS profile instead. UDP through a CONNECT-UDP (MASQUE) proxy is the exception: auth goes on the CONNECT-UDP request to the proxy. A JWT-SVID is fetched and checked before a session is prepared, so a failed check or an unreachable Workload API ends it before any traffic, as for a single request.

Authorization Code with PKCE

  • Sign-in opens the system browser, never an embedded web view. The redirect goes to a one-time listener at http://127.0.0.1:<port>/callback.
  • API owners register a public client that allows the loopback redirect with any port. Issuers that only accept a fixed port are not supported by this flow.
  • Authorization and token URLs must use https, except a loopback test issuer.
  • The code exchange uses the request's own TLS profile, proxy and timeouts.
  • Without a valid or refreshable token, a send fails locally with “Sign in before sending with this OAuth profile” and nothing reaches the network. It never falls back to client credentials.
  • Tokens live in memory only: they are cleared on lock, never written to disk and never exported, so you sign in again after a restart.

Signing in to an API does not sign you in to Anvil, and neither identity changes how a gateway authenticates to its own backend.

Verification On by Default

  • Private CA roots belong to a TLS profile, so trusting a CA for one workspace does not trust it for another.
  • mTLS client certificates (PEM or PKCS#12) are bound to hosts. A connection that presented a certificate is not reused by requests without that identity.
  • Credentials are stripped when a redirect crosses origins.
  • For mesh workloads, a TLS profile can verify the server's X.509-SVID by SPIFFE ID or trust domain instead of its host name, and can override the SNI. Host-name verification stays on unless the profile sets a SPIFFE identity.
  • A TLS profile's client identity can also be an X.509-SVID fetched from a SPIFFE Workload API; see below.
  • 0-RTT early data is off by default. Turned on for a settings layer or a run (anvil send --early-data), a new connection can resume a session and send GET, HEAD or OPTIONS as early data, plus PUT, DELETE or TRACE only if you list them; a policy that lists any other method is refused. HTTP/3 uses QUIC 0-RTT; over TCP, TLS 1.3 early data needs the HTTP/1.1-only or HTTP/2-only policy. A 425 Too Early gets at most one retry, after the handshake (RFC 8470). Session tickets are kept in memory only and cleared on lock.
  • Certificate problems on your own connection are reported as Confirmed with the phase and the certificate evidence; the private key never appears in a record.
Ferrum Anvil diagnosis of an HTTPS request to a local test server: no response, transport failed, dispatch not dispatched. The finding The server certificate is not trusted is marked Confirmed, scoped Your connection to destination, owned by the caller, and explains that the certificate from a throwaway test CA does not chain to a trusted authority in this TLS profile and that it failed before any HTTP was exchanged. It lists other possibilities, such as a private CA missing from the profile or a server that does not send its intermediate certificates.
Pre-release build. A certificate signed by a throwaway test CA; the remediation does not suggest turning verification off.

Identities from the Workload API

Anvil can ask a SPIFFE Workload API for the identity it issues to Anvil, and use it as a TLS client identity or as a bearer token.

Endpoint

  • A Unix socket, set on the profile as unix:///path/to/socket or taken from the SPIFFE_ENDPOINT_SOCKET environment variable; the evidence records which. tcp:// endpoints are refused, because the server could not attest the caller over TCP. Windows named-pipe endpoints are implemented but checked only by CI.
  • The server attests Anvil by the socket's peer credentials. When it issues no identity, the finding names the uid Anvil presented.
  • An unreachable endpoint, a refusal or any other failure stops the request before anything is sent, so the destination is never blamed.
  • anvil workload probe and the editors' Test the Workload API button show what the endpoint issues to Anvil without keeping or showing a key or a token.

X.509-SVID as a client identity

  • A TLS profile's client identity can be the X.509-SVID the Workload API issues, instead of a PEM or PKCS#12 file. It is used wherever that profile presents an identity, including DTLS, a MASQUE proxy and an HBONE endpoint. Host bindings apply as for any identity.
  • Optionally, the SVID's own trust-domain bundle joins the profile's CA certificates, so a mesh server can be verified by SPIFFE ID. Federated bundles are recorded but never trusted.
  • SVIDs are cached in memory and fetched again after half their lifetime.

JWT-SVID auth

  • Sends Authorization: Bearer <JWT-SVID>; the header and prefix are configurable. The token comes from the Workload API for the audiences you set, from a variable or vault value, or from a file read at send time.
  • Before sending, Anvil checks the token locally: its format; its algorithm (never none or HMAC); a subject that is a SPIFFE ID, and the one you set, if any; every configured audience; expiry and not-before by this machine's clock; and, when enabled, the signature against the trust domain's bundle from the Workload API.
  • A failed check stops the request with nothing sent, unless the profile explicitly allows sending anyway, to see how a verifier treats a bad token. Imports turn that setting off.
  • A 401 after sending a JWT-SVID gets a finding with unknown confidence. It quotes the public body and lists the local checks as evidence, but never claims the verifier's reason: Ferrum Edge's jwks_auth plugin, for example, answers every rejected token with the same body.
  • The token and private keys are never recorded; the evidence keeps the decoded subject, audience, algorithm, key id and times. Tokens, SVIDs and bundles live in memory only and are cleared on lock.

How this is checked

In the lab, Anvil uses what Ferrum Edge's own Workload API issues to it against the same release's mesh listener and a jwks_auth route, on the supported 0.9.8, 0.9.7 and 0.9.5 releases. That Workload API is off by default and is a development and test feature of the gateway. Automated tests also run against an independent Workload API test server. SPIRE itself is not in the lab.

A Scoped Bypass, With a Warning

Turning verification off is an explicit TLS profile setting, not a global switch, and Anvil never recommends it as a fix.

  • A request that uses the bypass succeeds with an insecure-TLS warning and a client.tls.verification_bypassed finding that still records whether strict verification would have failed.
  • The next request under a strict profile fails as usual and carries no warning: the bypass does not leak.
  • Plain HTTP is different from verification off: plain HTTP has no TLS session and no insecure-TLS warning; a bypass has both.
  • Imports re-enable verification: a bundle cannot switch a bypass on.
  • For DTLS, a bypass keeps encryption and records that verification was skipped.