Proposal: SPIFFE and SPIRE Integration for nono #1002
Replies: 3 comments 2 replies
-
|
shut up and take my money. big vote for having this, it would close real existing gaps |
Beta Was this translation helpful? Give feedback.
-
|
Yes, I'd love that support for agents. Can add the ability to authenticate to MCP servers using SVIDs, fetch creds from centralized store, and do all sorts of auth, including WIF if needed. off-topic: How did you generate the diagram? I love it! |
Beta Was this translation helpful? Give feedback.
-
|
This look great, we can see value that could be on both sides. Use the SVID inside the sandbox for authn/z and outside for signing. That signing could be done by Witness too, as we've got build capture/attestor workflows for this we'd extend. /cc @jkjell @06kellyjac |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
SPIFFE and SPIRE can integrate with nono and fill a key gap in nono's capability - giving workload identity to the unsandboxed nono supervisor/proxy, preventing the handing of SPIFFE credentials directly to the sandboxed agent.
So nono already has a strong split:
nono-proxyOne approach is to extend
nono-proxyso routes can authenticate to upstream services with SPIFFE X.509-SVIDs or JWT-SVIDs obtained from a local SPIRE Workload API (outside the sandboxed child). This way the sandboxed agent continues to see only a localhost HTTP interface but with phantom credentials. Real workload identity material remains outside the sandbox, leveraging the supervisors existing backends.Existing nono Fit (why SPIFFE / SPIRE and nono = best friends).
The current architecture already has the pieces needed for a clean integration ("so much room for activities"):
nonocore exposesCapabilitySetandNetworkMode, includingProxyOnly.nono-clidecides when the proxy is active and grants only the capabilities needed by the child.nono-proxyowns reverse proxy routes, endpoint filtering, credential loading, OAuth2 token exchange, TLS connectors, and audit events.tls_client_certandtls_client_key.This strongly suggests SPIFFE should be a CLI/proxy feature, but having primitives in the core nono crate is interesting for the nono libs (py,ts,go) - so we should see what's possible there.
So what's the value?
A good many Organizations / kube shops that already use SPIFFE/SPIRE we also want capability control of AI agents ability to call internal services without long-lived API keys. Today, nono can keep static credentials out of the sandbox by proxying and injecting them, but there is no first-class way to:
Of course the dangerous shortcut is to grant the sandboxed child read/write access to the SPIRE agent socket. That may be acceptable in some deployments, but it changes nono's threat model, so it's a definite "nono": the agent can obtain identity material and use it without the proxy's endpoint filtering, audit mediation, or phantom-token boundary , giving us a supervisor boundary - to use the due an update image, it sits alongside the audit and cred store
Goals
Non-Goals
/run/spire/sockets/agent.sockby default. "i will never do such a thing"Possible
PLease review SPIFFE experts.
Add SPIFFE-aware route authentication to
nono-proxy, configured through profiles and manifests.The sandboxed agent still calls:
The proxy:
Design
1. X.509-SVID Route Authentication
Add a route auth mode for upstream mTLS:
{ "network": { "custom_credentials": { "internal-api": { "upstream": "https://api.internal.example", "auth": { "type": "spiffe_x509", "workload_api_socket": "/run/spire/sockets/agent.sock", "svid_hint": "internal", "expected_upstream_spiffe_id": "spiffe://prod.example/internal/api" }, "endpoint_rules": [ { "method": "GET", "path": "/v1/tasks/**" }, { "method": "POST", "path": "/v1/tasks/*/comments" } ] } }, "credentials": ["internal-api"] } }Behavior:
nono-proxyconnects to the SPIRE Workload API over UDS.svid_hintselects the intended identity; otherwise the SPIFFE default identity behavior applies.rustlsclient config using the SVID certificate chain, private key, and trust bundle.expected_upstream_spiffe_idwhen present.Must-haves:
2. JWT-SVID Route Authentication
Add a route auth mode for bearer-style identity:
{ "network": { "custom_credentials": { "inventory": { "upstream": "https://inventory.internal.example", "auth": { "type": "spiffe_jwt", "workload_api_socket": "/run/spire/sockets/agent.sock", "audience": ["inventory.internal.example"], "inject_header": "Authorization", "credential_format": "Bearer {}" }, "endpoint_rules": [ { "method": "GET", "path": "/v1/items/**" } ] } }, "credentials": ["inventory"] } }Behavior:
This is useful for services that do not accept client certificate mTLS but can validate SPIFFE JWTs, or for token-exchange flows where a JWT-SVID is used as a client assertion.
3. JWT-SVID as OAuth2 Client Assertion
Extend the existing OAuth2 route support with a SPIFFE assertion source:
{ "oauth2": { "token_url": "https://auth.internal.example/oauth/token", "client_assertion": { "type": "spiffe_jwt", "audience": ["https://auth.internal.example/oauth/token"] }, "scope": "tasks:read tasks:comment" } }The proxy exchanges the JWT-SVID for an access token, caches the token, and injects the resulting access token upstream. This keeps compatibility with APIs that standardize on OAuth2 while still eliminating long-lived client secrets.
4. Session and Audit Identity Context
Add optional SPIFFE fields to session and audit records:
{ "spiffe": { "workload_id": "spiffe://prod.example/nono/proxy", "trust_domain": "prod.example", "svid_type": "x509", "source": "spire-workload-api" } }Network audit events should include route-level identity context:
{ "route": "internal-api", "auth": { "mechanism": "spiffe_x509", "source_spiffe_id": "spiffe://prod.example/nono/proxy", "upstream_spiffe_id": "spiffe://prod.example/internal/api" } }We use the scrubber to ensure audit record should not contain private keys, JWT bodies, raw certificates unless explicitly scrubbed, or full SVID material.
SPIRE Attestation Considerations
SPIRE issues workload identity by matching attested selectors against registration entries. Plain Unix selectors such as UID, GID, and executable path may not distinguish:
Both may have the same binary path and Unix user.
Possible deployment models:
Proxy identity model
SPIRE identifies the nono supervisor/proxy as the workload. nono then enforces per-profile and per-route policy internally. This is the initial recommended model.
Kubernetes identity model
SPIRE uses Kubernetes selectors such as namespace, service account, pod labels, and container name. nono handles finer-grained policy inside the pod.
nono-aware SPIRE attestor
A future SPIRE workload attestor could inspect nono session metadata and expose selectors such as profile name, manifest digest, package identity, or audit session ID. This should be considered only after the proxy identity model proves insufficient.
impact of AF-UNIX Pathname Mediation (update from 2026-05-22)
AF-UNIX pathname mediation has landed. This changes the
threat model meaningfully and closes several questions that were left open in
the original proposal.
The supervisor now installs a seccomp-notify filter (Linux) that intercepts
every
connect(2)andbind(2)syscall from the sandboxed child. The filterroutes calls through the supervisor's decision function, which:
UnixSocketCapabilityallowlist that the CLI builds from profile fields.unnamed sockets, and any pathname socket not in the allowlist.
The profile surface is:
{ "linux": { "af_unix_mediation": "pathname" }, "unix_socket": ["/run/spire/sockets/agent.sock"] }Without
unix_socketgranting the SPIRE socket, the child receivesEACCESon any connect attempt, and the denial is captured in the session audit as an
IpcDenialRecordwith path, operation, and suggested remediation flags.What this changes for the SPIFFE integration
Explicit Socket Access Policy is now backed by active OS-level
enforcement, not just capability absence. The original proposal described a
passive model where the SPIRE socket was simply not granted; the mediated model
intercepts and logs actual attempts. The threat of an agent discovering and
trying to open
/run/spire/sockets/agent.sockby inspecting/procorenvironment variables is now stopped at the syscall boundary with an audit
record, not silently ignored.
The opt-in mechanism the proposal described — an explicit CLI flag or profile
field — maps directly onto
unix_socketgrants. There is no separateexpose_workload_api_to_childtoggle needed; the existingUnixSocketCapabilitygrant model covers it cleanly.
macOS gap
AF-UNIX pathname mediation is Linux-only. On macOS, Seatbelt controls socket
access via the
(deny network-outbound (path-literal ...))predicate or(deny ipc-posix*)rules. The proxy-owned SPIRE socket model still applieson macOS, but the active intercept-and-audit layer is absent.
Beta Was this translation helpful? Give feedback.
All reactions