Transparent CA certificate injection for Kubernetes containers using containerd NRI (Node Resource Interface).
Fork of tsuzu/cainjekt — maintained by natrontech with configurable annotation prefix, Helm chart, and additional tooling.
- Inject custom CA certificates into containers at runtime — no image modifications needed
- Uses containerd NRI for transparent integration with the container runtime
- Per-container dynamic CA bundle staging with atomic writes and symlink protection
- Supports multiple OS distributions (Debian, Ubuntu, Alpine, RHEL, Fedora, Arch, openSUSE)
- Language-specific processors for Go, Java, Node.js, Python, and Ruby
- Prometheus metrics endpoint (
/metrics), health (/healthz) and readiness (/readyz) probes - Namespace-level opt-in via labels (in addition to per-pod annotations)
- Configurable annotation prefix (default:
cainjekt.natron.io) - Minimal distroless container image (~15MB)
- Multi-architecture support (amd64, arm64)
helm install cainjekt oci://ghcr.io/natrontech/charts/cainjekt \
--namespace kube-system \
--set-file caBundle=/path/to/ca-bundle.pem# Create CA bundle ConfigMap
kubectl create configmap cainjekt-ca-bundle \
--from-file=ca-bundle.pem=/path/to/ca-bundle.pem \
--namespace=kube-system
# Deploy
kubectl apply -k deploy/kubernetes/Add the annotation to opt in:
apiVersion: v1
kind: Pod
metadata:
name: my-app
annotations:
cainjekt.natron.io/enabled: "true"
spec:
containers:
- name: app
image: my-app:latestYou can also filter which processors run:
annotations:
cainjekt.natron.io/enabled: "true"
cainjekt.natron.io/processors.include: "os-debian,lang-python"
cainjekt.natron.io/processors.exclude: "os-fallback"cainjekt runs as a DaemonSet on every node. When a container with the opt-in annotation starts:
- NRI Plugin intercepts container creation, stages a per-container CA file, and injects an OCI hook + wrapper binary
- OCI Hook runs before the container starts — detects the OS distro and merges the CA into system trust stores (e.g.
/etc/ssl/certs/ca-certificates.crt) - Wrapper runs as the container entrypoint — sets language-specific environment variables and then
execs the original command
This three-stage pipeline ensures both OS-level and language-level trust stores are updated transparently.
Pre-built images on GitHub Container Registry:
ghcr.io/natrontech/cainjekt:<version>
ghcr.io/natrontech/cainjekt-installer:<version>
Both linux/amd64 and linux/arm64 platforms are supported.
| Environment Variable | Default | Description |
|---|---|---|
CAINJEKT_CA_FILE |
/etc/cainjekt/ca-bundle.pem |
Path to CA bundle |
CAINJEKT_ANNOTATION_PREFIX |
cainjekt.natron.io |
Annotation prefix for pod opt-in |
CAINJEKT_FAIL_POLICY |
fail-open |
fail-open or fail-closed |
CAINJEKT_LOG_LEVEL |
info |
Log level: debug, info, warn, error |
CAINJEKT_HOOK_TIMEOUT_SEC |
5 |
OCI hook timeout in seconds (containerd kills hook if exceeded) |
CAINJEKT_DYNAMIC_CA_ROOT |
/run/cainjekt/containers |
Per-container CA staging root |
| Annotation | Description |
|---|---|
<prefix>/enabled: "true" |
Enable CA injection for this pod |
<prefix>/processors.include |
Comma-separated processor allow list |
<prefix>/processors.exclude |
Comma-separated processor deny list |
<prefix>/exclude-containers |
Comma-separated container names to skip (e.g. istio-proxy,linkerd-proxy) |
- Static binaries (Go, Rust): CA verification is compiled in — system stores are ignored
- Distroless/scratch images: No
/etc/os-release, limited writable filesystem — fallback processor may not detect the correct trust store - Read-only root filesystems: OS trust store cannot be modified, but language processors (Java, Node.js, Python) still work via env vars pointing to the dynamic CA path
- Fail-open default: Failed injection is silent — container starts without CA (check pod logs for warnings)
- Nodes without NRI: Some managed Kubernetes nodes (e.g. AKS GPU nodes) don't have NRI enabled in containerd. The plugin will crash-loop on these nodes — exclude them via
nodeSelectororaffinityin the Helm values
| Processor | Env Var Set | Detection |
|---|---|---|
lang-go |
SSL_CERT_FILE |
/usr/local/go/bin/go |
lang-java |
JAVA_TOOL_OPTIONS (trustStore + PEM type, JDK 18+) |
/usr/bin/java |
lang-nodejs |
NODE_EXTRA_CA_CERTS |
/usr/bin/node |
lang-python |
SSL_CERT_FILE, REQUESTS_CA_BUNDLE |
/usr/bin/python3 |
lang-ruby |
SSL_CERT_FILE |
/usr/bin/ruby |
The NRI plugin exposes a Prometheus-compatible metrics endpoint on :9443:
GET /metrics— Prometheus metrics (injection counts, errors, processor stats, active containers)GET /healthz— liveness probeGET /readyz— readiness probe
Enable Prometheus Operator scraping via Helm:
helm install cainjekt charts/cainjekt \
--set serviceMonitor.enabled=true \
--set serviceMonitor.labels.release=prometheusmake build # Build binary
make docker-build # Build Docker images
make test # Unit tests
make lint # golangci-lint
make helm-lint # Lint Helm chart- Architecture Deep-Dive — injection pipeline, processor system, security model
- Usage Guide — installation, configuration, verification, troubleshooting
- Why We Forked — what changed vs upstream and why
- Kubernetes Deployment (kustomize) — step-by-step kustomize deployment
- Kubernetes cluster with containerd runtime
- containerd v2.0+ (NRI enabled by default) or v1.x with NRI manually enabled