Signet is a narrow, CRD-free X.509-SVID issuer built specifically for Interlink workloads on Kubernetes. It watches the built-in certificates.k8s.io/v1 CertificateSigningRequest API, derives one SPIFFE identity from the authenticated Kubernetes service account, approves requests that satisfy the fixed profile, and writes a directly signed leaf certificate to CSR status.
Signet is not a general-purpose PKI, cert-manager replacement, admission webhook, or network certificate service.
A request is accepted only when all of these facts agree:
spec.signerNameis Signet's configured custom signer.- The API-server-populated username is
system:serviceaccount:<namespace>:<name>and that service account is explicitly allowlisted. spec.usagesis exactlydigital signature,server auth, andclient auth.spec.expirationSecondsis between 600 seconds and the configured maximum.spec.requestis one RFC 7468CERTIFICATE REQUESTcontaining canonical PKCS #10 v1 DER with a valid Ed25519 proof-of-possession signature.- The request has an empty subject and exactly one URI SAN.
- The URI is exactly
spiffe://<trust-domain>/ns/<namespace>/sa/<name>, derived from the authenticated username rather than trusted from the CSR.
Every mismatch is denied with a stable reason code. Signet never copies a requested subject, DNS name, IP address, email address, CA bit, arbitrary extension, or arbitrary lifetime into a certificate.
The online signing CA is intentionally simple: a persistent Ed25519 key in one Kubernetes Secret. Protect that Secret with Kubernetes encryption at rest, tightly limit API access, and keep a protected offline backup. A compromised Signet pod or CA Secret can issue credentials within the CA's trust domain. Signet does not provide HSM integration, revocation, or automatic CA rotation.
The supplied Deployment runs one replica deliberately. Multiple uncoordinated replicas could sign the same newly observed CSR before either status update becomes visible. Kubernetes restarts the single replica, and rolling updates use a surge pod, but there is no leader election.
See RFC_PROFILE.md for the complete certificate, request, and signer contract. The profile is backed by archived RFC Editor source texts and checksums in rfcs/.
sequenceDiagram
participant W as Interlink workload
participant K as Kubernetes API
participant S as Signet
W->>W: Generate Ed25519 key and PKCS #10 request
W->>K: Create CertificateSigningRequest
K->>K: Record authenticated service-account username
S->>K: Watch Signet CSRs
S->>S: Validate policy, proof of possession, and SPIFFE URI
S->>K: Add Approved or Denied condition
S->>K: Write signed leaf to status.certificate
W->>K: Read issued certificate
Signet exposes only /healthz and /readyz for kubelet probes. Certificate operations use the Kubernetes API; there is no Signet service endpoint.
The root and leaf profiles are fixed rather than configurable:
| Property | Signing CA | Interlink leaf |
|---|---|---|
| Key and signature | Ed25519 | Ed25519 |
| Subject | Configured non-empty common name | Empty |
| SAN | None | One critical SPIFFE URI |
| Basic constraints | Critical, cA=TRUE, pathLen=0 |
Critical, cA=FALSE |
| Key usage | Critical keyCertSign, cRLSign |
Critical digitalSignature |
| Extended key usage | None | serverAuth, clientAuth |
| Serial | Positive, non-zero, at most 20 octets | Positive, non-zero, at most 20 octets |
| Chain | Self-signed trust anchor | Directly signed by the configured trust anchor |
| Leaf lifetime | Not applicable | Exact requested interval, at most 24 hours by default, with a five-minute notBefore backdate |
Interlink currently transports one certificate, so Signet emits a directly signed leaf rather than a leaf-plus-intermediate chain. The trust anchor is distributed separately and is never placed in CSR status.
Prerequisites: Kubernetes v1.22 or later and kubectl with Kustomize support.
-
Choose an immutable container image. CI publishes one OCI manifest for both
linux/amd64andlinux/arm64toghcr.io/alexandernicholson/signetonly after every check passes. A push tomainpublisheslatestandsha-<full-commit>; av*tag also publishes the tag and semantic-version aliases. Pull by digest in production:docker pull ghcr.io/alexandernicholson/signet:latest docker buildx imagetools inspect ghcr.io/alexandernicholson/signet:latest
Pull requests still build and smoke-test the Dockerfile but cannot publish. A local single-platform image can be built with
docker build -t signet .. -
Generate the persistent CA locally. The command refuses to overwrite any existing output:
cargo run --release -- bootstrap \ --output-dir deploy/ca \ --common-name "Interlink production root" \ --validity-days 3650deploy/ca/is gitignored. Back upca.keysecurely before deployment. -
Edit
deploy/signet.yaml:- Set
SIGNET_TRUST_DOMAIN. - Set the exact comma-separated
SIGNET_ALLOW_SERVICE_ACCOUNTSallowlist. - Set
SIGNET_SIGNER_NAMEif the default is not suitable. - If the signer name changes, update the ClusterRole
signers.resourceNamesentry to the same value. Kubernetes checks the virtualapproveandsignverbs against that exact signer. - Pin
imageto the digest you published.
- Set
-
Review the rendered objects, then apply them:
kubectl kustomize deploy > /tmp/signet-rendered.yaml kubectl apply -k deploy kubectl -n signet-system rollout status deployment/signet kubectl -n signet-system get pods -
Distribute the public trust bundle to the namespace that runs Interlink. ConfigMaps cannot be mounted across namespaces; the generated
signet-system/interlink-trust-bundleis a source copy, not an automatic workload mount. For a workload inworkloads:kubectl -n workloads create configmap interlink-trust-bundle \ --from-file=ca-bundle.pem=deploy/ca/bundle.pem \ --dry-run=client -o yaml | kubectl apply -f -Mount that ConfigMap and configure Interlink to trust
ca-bundle.pem. Never mountca.keyinto a workload pod. -
Confirm the controller is ready and inspect CSR outcomes:
kubectl -n signet-system get deployment signet kubectl get certificatesigningrequests kubectl describe certificatesigningrequest <name>
The manifest grants only cluster-wide CSR read access, approval/status mutation, and signer-scoped approve/sign authorization. The CA volume is group-readable only by the non-root Signet process.
signet run accepts flags or matching environment variables:
| Setting | Default | Meaning |
|---|---|---|
SIGNET_CA_CERTIFICATE |
/var/run/signet-ca/ca.crt |
Signing CA certificate PEM |
SIGNET_CA_PRIVATE_KEY |
/var/run/signet-ca/ca.key |
Matching PKCS #8 Ed25519 private key PEM |
SIGNET_SIGNER_NAME |
example.com/signet |
Custom Kubernetes signer name |
SIGNET_TRUST_DOMAIN |
Required | SPIFFE trust domain |
SIGNET_ALLOW_SERVICE_ACCOUNTS |
Required | Comma-separated <namespace>:<name> allowlist |
SIGNET_MAX_TTL_SECONDS |
86400 |
Maximum requested certificate interval |
SIGNET_HEALTH_ADDRESS |
0.0.0.0:8080 |
Probe listener |
Readiness remains false until the initial Kubernetes CSR watch has completed. Liveness reports immediately after the probe listener starts.
Run signet agent in the Interlink pod to generate its Ed25519 private key,
submit the exact Signet CSR profile, poll the named CSR, validate the returned
certificate and trust chain, and write tls.key and tls.crt to a shared
volume. The namespace and service-account arguments are mandatory, local
configuration; the API server independently records the authenticated
service-account username on the CSR, and Signet requires both identities to
match.
/signet agent \
--trust-domain example.com \
--namespace workloads \
--service-account example-app \
--ca-bundle /var/run/interlink/ca-bundle.pem \
--output-dir /var/run/interlink/identity \
--reload-address 127.0.0.1:4192Continuous mode renews before expiry. After each newly issued credential is
durably installed, it sends a bounded HTTP/1.1 POST /reload request to the
configured loopback address and retries failures. Use --once in an init
container to acquire one credential and exit without sending a reload request.
The private key never enters Kubernetes; only the CSR and issued certificate
do.
The workload service account needs only create and get on the cluster-scoped
certificatesigningrequests.certificates.k8s.io resource. It does not need
approval, status-update, list, watch, or Secret permissions. Mount the public CA
bundle read-only and place the output directory on a volume shared with
Interlink.
Configure the Interlink container to read the same files with
INTERLINK_CA_BUNDLE_PATH=/var/run/interlink/ca-bundle.pem,
INTERLINK_CERT_PATH=/var/run/interlink/identity/tls.crt, and
INTERLINK_KEY_PATH=/var/run/interlink/identity/tls.key. Set
INTERLINK_IDENTITY to the SPIFFE ID formed from the agent's trust-domain,
namespace, and service-account arguments. The pod must give both containers a
shared supplementary group (for example with fsGroup) because the agent
writes the private key with mode 0640.
| Setting | Default | Meaning |
|---|---|---|
SIGNET_SIGNER_NAME |
example.com/signet |
Must equal the issuer's signer name |
SIGNET_TRUST_DOMAIN |
Required | SPIFFE trust domain |
SIGNET_AGENT_NAMESPACE |
Required | Workload namespace |
SIGNET_AGENT_SERVICE_ACCOUNT |
Required | Workload service-account name |
SIGNET_AGENT_CA_BUNDLE |
/var/run/interlink/ca-bundle.pem |
One or more trusted Signet CA PEM blocks |
SIGNET_AGENT_OUTPUT_DIR |
/var/run/interlink/identity |
Directory receiving tls.key and tls.crt |
SIGNET_AGENT_EXPIRATION_SECONDS |
86400 |
Exact requested certificate interval |
SIGNET_AGENT_RENEW_BEFORE_SECONDS |
43200 |
Renewal lead time |
SIGNET_AGENT_REQUEST_TIMEOUT_SECONDS |
30 |
CSR and reload timeout |
SIGNET_AGENT_POLL_INTERVAL_MILLIS |
250 |
CSR and reload-retry interval |
SIGNET_AGENT_RELOAD_ADDRESS |
127.0.0.1:4192 |
Interlink reload listener |
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-targets
cargo llvm-cov --all-targets --fail-under-lines 100The coverage gate enforces 100% source-line coverage. Every unit and integration test has a two-second deadline. Tests exercise the RFC profile, malformed and adversarial CSRs, CA persistence and validation, Kubernetes subresource patches, watch lifecycle, graceful shutdown, health behavior, CLI failures, deployment resource generation, and rollback after partial writes.
For reproducible hot-path commands and measured optimization rounds, see BENCHMARKS.md.
- Kubernetes Certificates and Certificate Signing Requests
- RFC 2986: PKCS #10
- RFC 3986: URI syntax
- RFC 5280 and RFC 6818: Internet X.509 PKI
- RFC 5958: Asymmetric Key Packages
- RFC 7468: PKIX textual encodings
- RFC 8410: Ed25519 in PKIX
MIT