-
Notifications
You must be signed in to change notification settings - Fork 30
mTLS
If the niks3 server sits behind a reverse proxy that verifies client certificates, the certificate is the credential — no bearer token required. This is a good fit for fleets of machines that already manage X.509 certs (internal CA, step-ca, vault PKI, SPIFFE).
Pass the cert and skip the auth flags entirely:
niks3 push --server-url https://niks3.example.com \
--client-cert /etc/niks3/client.pem \
--client-key /etc/niks3/client.key \
--ca-cert /etc/niks3/ca.pem \
./result--ca-cert is optional — without it the system CA pool is used to verify
the server. The niks3-hook serve daemon takes the same flags.
For Nix to read from the cache through a vhost that requires a client
cert (require = true below), set the cert in nix.conf:
ssl-cert-file = /etc/niks3/client.pem
ssl-key-file = /etc/niks3/client.keyThere are two ways for the server to learn the verified client cert:
The server can terminate TLS itself and verify client certs directly:
niks3-server \
--tls-cert /etc/niks3/server.pem \
--tls-key /etc/niks3/server.key \
--tls-client-ca /etc/niks3/client-ca.pem \
--mtls-bound-subject 'CN=ci-runner-*' \
...This is simpler and safer (no header trust) but you give up nginx's ACME, HTTP/2, and rate-limiting features. Best for internal deployments behind a firewall.
When a reverse proxy terminates TLS, it verifies the client cert and
signals the result via headers. niks3 trusts those headers because
--mtls-proxy-header opts in.
niks3-server \
--mtls-proxy-header X-SSL-Client-Verify \
--mtls-bound-subject 'CN=ci-runner-*' \
...Security:
--mtls-proxy-headermust only be enabled behind a proxy that overrides the header on every request — including requests where verification failed or no cert was presented. Otherwise any client can fakeX-SSL-Client-Verify: SUCCESSand bypass auth entirely.
| Flag | Default | Description |
|---|---|---|
--tls-cert / --tls-key
|
(off) | Terminate TLS in the server instead of a proxy |
--tls-client-ca |
(off) | CA bundle for native mTLS client verification |
--mtls-proxy-header |
(off) | Header set to SUCCESS by the proxy after verifying the client cert |
--mtls-subject-header |
X-SSL-Client-Dn |
Header carrying the verified cert's subject DN |
--mtls-bound-subject |
(any) | Restrict write auth to certs whose subject DN matches this glob; repeatable |
--mtls-bound-subject-read |
(public) | Gate the read proxy behind mTLS for matching cert DNs; repeatable. Empty = public reads |
--tls-client-ca and --mtls-proxy-header are mutually exclusive.
Subject DN format: native TLS and nginx (≥1.11.6) both emit RFC 4514 DNs, e.g.
CN=ci-runner-1,O=Acme. Older nginx or the_legacyvariable uses OpenSSL's slash format. Run with--debugto see the exact string the server matches against, or use a glob like*CN=ci-runner*.
services.niks3.nginx = {
enable = true;
domain = "niks3.example.com";
mtls = {
enable = true;
clientCAFile = "/etc/niks3/client-ca.pem";
boundSubjects = [ "CN=ci-runner-*" ];
# boundSubjectsRead = [ "CN=*,O=Acme" ]; # gate reads too (private cache)
# require = true; # `optional` (default) lets bearer-token auth coexist
};
};This configures nginx with ssl_verify_client, always sets
X-SSL-Client-Verify and X-SSL-Client-Dn on proxied requests (overriding
any inbound values), and passes the matching --mtls-* flags to the
server.
With require = false (the default, nginx ssl_verify_client optional),
clients without a cert can still authenticate with a bearer token.
With require = true, every TLS connection — including Nix substituter
reads — must present a cert.
By default the read proxy is public: Nix substituters present no
credentials and the cache contents are integrity-signed. Set
boundSubjectsRead to gate reads behind mTLS — useful for caches that
must not leak which derivations exist.
The write and read allowlists are independent: a CI runner cert can be
allowed to push but not browse, or vice versa. Setting boundSubjectsRead
implies require = true since every read must present a cert.
- mTLS: long-lived machines with managed certs, no external identity provider needed, both sides verify each other.
- OIDC: short-lived CI jobs (GitHub Actions, GitLab CI), no certs to provision, identity comes from the CI platform.
- API token: simplest, but a shared secret. Good for bootstrapping or a single trusted operator.