Skip to content

About

SPIFFE/SPIRE workload identity agent for Zentinel reverse proxy

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

27 Commits

Folders and files

Repository files navigation

Zentinel

SPIFFE agent

Reads an X.509 SVID from a client-certificate header at the request-headers phase, checks the SPIFFE ID against an allowlist, and passes the identity to the upstream as headers.

CI License: Apache-2.0 Rust: 1.94.1+ Agent protocol: v2

Registry · Docs · All repositories

Warning

Do not rely on this agent for authentication yet. Its policy (allowlist, failure modes, header names; require-mtls also has a flag) can only be set through the agent protocol's configure event. Zentinel 26.10_8 and later send the agent block's config { } on every connection (releases up to 0.6.50 never did); on those releases, or without a config { } block, the agent runs with its defaults: mTLS not required, an empty allowlist that allows every SPIFFE ID, and no connection to the SPIRE Workload API, so certificates are not verified against a trust bundle (only parsed, checked for expiry and for a SPIFFE ID). Zentinel also does not set X-Forwarded-Client-Cert itself, so the header the agent reads is whatever the client or a TLS-terminating hop in front of Zentinel sent.

Until the agent can verify a forwarded certificate against the SPIRE trust bundle (#5), it fails closed: it never treats X-Forwarded-Client-Cert as identity, so it never sets the identity headers, and with require-mtls it denies every request with 401. The opt-in tls.trust-forwarded-client-cert is refused. Client-supplied identity headers are always removed.

For service-to-service traffic where each workload holds a SPIFFE X.509 SVID. The agent takes the certificate from X-Forwarded-Client-Cert (PEM, URL-encoded PEM, or base64 DER), validates it, matches the SPIFFE ID against an allowlist (exact, prefix, regex, trust domain), and on success adds X-SPIFFE-Id, X-SPIFFE-Trust-Domain, X-SPIFFE-Workload-Id, X-Auth-Method and X-Auth-Timestamp to the upstream request. A rejected certificate gets 401, an ID outside the allowlist 403, both with WWW-Authenticate: SPIFFE.

Quick start

zentinel bundle install spiffe
zentinel-spiffe-agent \
  --socket /var/run/zentinel/spiffe.sock \
  --spire-socket /run/spire/sockets/agent.sock
system {
    worker-threads 0
}

listeners {
    listener "http" {
        address "127.0.0.1:8080"
        protocol "http"
    }
}

agents {
    agent "spiffe" type="auth" {
        unix-socket "/var/run/zentinel/spiffe.sock"
        events "request_headers"
        timeout-ms 100
        failure-mode "closed"
    }
}

filters {
    filter "spiffe" {
        type "agent"
        agent "spiffe"
        failure-mode "closed"
    }
}

routes {
    route "api" {
        matches {
            path-prefix "/api/"
        }
        upstream "backend"
        filters "spiffe"
    }
}

upstreams {
    upstream "backend" {
        target "127.0.0.1:3000"
    }
}

See the warning above for what this enforces today.

Install

Bundle (recommended). Downloads the release binary for your platform:

zentinel bundle install spiffe

See the bundle documentation.

Cargo, from the repository. The crate is not on crates.io; this builds the zentinel-spiffe-agent binary from main (Rust 1.94.1 or newer, and protoc for the Workload API bindings):

cargo install --git https://github.com/zentinelproxy/zentinel-agent-spiffe

Release binaries. Each release ships linux-x86_64, linux-aarch64 and darwin-aarch64 archives:

VERSION=0.3.0
PLATFORM=linux-x86_64   # or linux-aarch64, darwin-aarch64
curl -fsSL -o zentinel-spiffe-agent.tar.gz \
  "https://github.com/zentinelproxy/zentinel-agent-spiffe/releases/download/v${VERSION}/zentinel-spiffe-agent-${VERSION}-${PLATFORM}.tar.gz"
tar -xzf zentinel-spiffe-agent.tar.gz
sudo install -m 0755 zentinel-spiffe-agent /usr/local/bin/

From source.

git clone https://github.com/zentinelproxy/zentinel-agent-spiffe
cd zentinel-agent-spiffe
cargo build --release

Configuration

Command-line flags, the only settings read at startup:

Flag Env Default Meaning
--socket <PATH> AGENT_SOCKET /tmp/zentinel-spiffe.sock Unix socket to listen on
--grpc-address <ADDR> AGENT_GRPC_ADDRESS none Serve gRPC on this address instead of the socket
--spire-socket <PATH> SPIRE_AGENT_SOCKET /run/spire/sockets/agent.sock SPIRE agent socket; only connected to after a configure event
--require-mtls SPIFFE_REQUIRE_MTLS off Start with tls.require-mtls set (unreleased, main only). A configure event replaces the whole configuration, this setting included
-v, --verbose SPIFFE_VERBOSE off Debug logging

Settings accepted through the configure event (JSON, kebab-case), which Zentinel 26.10_8 and later fill from the agent block's config { } (over gRPC only a build against zentinel-agent-protocol 0.6.51 or later receives it, which the lockfile's 0.6.24 is not). A block the agent cannot parse, or one that sets tls.trust-forwarded-client-cert, is refused; Zentinel then does not use the agent and its filters' failure-mode applies. There is no --config file option, so apart from --require-mtls none of these can be set from the command line:

Option Default Meaning
spire.socket /run/spire/sockets/agent.sock SPIRE Workload API socket
spire.bundle-refresh-interval 300 Seconds between trust bundle refreshes
spire.api-timeout-ms 5000 Workload API timeout
tls.client-cert-header X-Forwarded-Client-Cert Header carrying the client certificate
tls.require-mtls false Reject requests without a verified identity (currently every request, 401); when false they pass without identity headers
tls.trust-forwarded-client-cert false Treat client-cert-header as identity because a trusted front proxy sets it. true is rejected until trust-bundle verification exists (#5)
headers.spiffe-id / trust-domain / workload-id / auth-method / auth-timestamp X-SPIFFE-Id / X-SPIFFE-Trust-Domain / X-SPIFFE-Workload-Id / X-Auth-Method / X-Auth-Timestamp Names of the headers added on success
allowlist.exact / prefix / trust-domains / patterns all empty Allowed SPIFFE IDs; all empty allows every ID
failure.spire-unavailable fail_closed fail_open turns any validation failure into an allow; cache behaves like fail_closed
failure.validation-failure reject log_and_allow lets certificates that fail validation through
audit.log-success true Log successful authentications

Parsed but not used: spire.svid-rotation-margin, failure.cache-ttl, audit.log-failures, audit.include-spiffe-id.

Proxy-side options (events, timeout-ms, failure-mode): https://docs.zentinelproxy.io/configuration/agents/. Agent reference: https://registry.zentinelproxy.io/spiffe/.

How it works

  1. Remove any client-supplied identity headers (the configured names and the defaults) from every request the agent lets through.
  2. Without trust-forwarded-client-cert (always, for now) there is no verifiable identity: allow without identity, or 401 when require-mtls is set, regardless of the failure modes.
  3. Read the certificate header (case-insensitive). Missing: allow, or 401 when require-mtls is set.
  4. Decode the certificate, require a spiffe:// URI SAN, reject it if expired.
  5. Require that a CA from the trust bundle for the ID's trust domain signed it; an empty bundle rejects it.
  6. Match the SPIFFE ID against the allowlist; outside it, 403.
  7. Allow, adding the identity headers to the upstream request.

The agent reports spiffe_agent_requests_total, ..._allowed_total, ..._blocked_total and spiffe_agent_spire_connected as v2 metrics, and reports itself degraded while the SPIRE socket does not exist.

Development

protoc is needed to build the Workload API bindings from proto/workload.proto.

cargo fmt --all --check
cargo clippy --all-targets -- -D warnings
cargo test

Minimum Rust: 1.94.1 (rust-version in Cargo.toml).

Related

License

Apache-2.0, see LICENSE.


Part of Zentinel, the security-first reverse proxy. The shisa guards the gate.

About

SPIFFE/SPIRE workload identity agent for Zentinel reverse proxy

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages