Skip to content

Wrapper Environment Variables

The cloudtaser wrapper is the process-wrapping binary that authenticates to OpenBao, fetches secrets into memory, and launches your application with secrets available as environment variables. These environment variables configure the wrapper's behaviour.

Automatic vs manual configuration

In Kubernetes, the mutating admission webhook sets all wrapper environment variables automatically based on pod annotations or a CloudTaserConfig CR. Manual configuration is only needed for systemd services, standalone testing, or non-Kubernetes environments.


OpenBao Connection

VAULT_ADDR

Required Yes
Default --
Example https://vault.eu-west-1.example.com:8200

The address of the OpenBao or OpenBao server. Must include the scheme (https://) and port.

VAULT_TOKEN

Required Only when VAULT_AUTH_METHOD=token
Default --

A pre-existing OpenBao token. Only used when the auth method is set to token. For Kubernetes deployments, the wrapper obtains a token automatically via Kubernetes auth.

VAULT_AUTH_METHOD

Required No
Default kubernetes
Values kubernetes, token

The authentication method used to obtain an OpenBao token.

  • kubernetes -- Authenticates using the pod's service account token (automatic in K8s).
  • token -- Uses a static token from VAULT_TOKEN.

VAULT_AUTH_ROLE

Required Yes (when VAULT_AUTH_METHOD=kubernetes)
Default --

The OpenBao Kubernetes auth role name. This role must be configured in OpenBao to accept service account tokens from the pod's namespace and service account.

VAULT_AUTH_MOUNT_PATH

Required No
Default auth/kubernetes

The mount path of the Kubernetes auth method in OpenBao.

VAULT_SKIP_VERIFY

Required No
Default false
Values true, false

Disables TLS certificate verification for the OpenBao connection.

Do not use in production

Disabling TLS verification removes a critical security control. An attacker can intercept secrets in transit with a man-in-the-middle attack. Use only in development environments with self-signed certificates.


Secret Configuration

CLOUDTASER_SECRET_PATHS

Required Yes
Default --
Example secret/data/myapp/db,secret/data/myapp/api

Comma-separated list of OpenBao KV secret paths to fetch. The wrapper reads all key-value pairs from each path.

CLOUDTASER_ENV_MAP

Required Yes
Default --
Example username=DB_USER,password=DB_PASS;api_key=STRIPE_KEY

Maps OpenBao secret fields to environment variable names. Semicolons separate mapping groups (one per secret path), commas separate individual field=VAR mappings within a group. See Annotations Reference for detailed format documentation.


Process Wrapping

CLOUDTASER_ORIGINAL_CMD

Required Yes
Default --
Example /usr/bin/node

The original command (entrypoint) of the application container. The wrapper launches this command after secrets have been fetched and placed into the process environment.

CLOUDTASER_ORIGINAL_ARGS

Required No
Default --
Example server.js,--port,8080

Comma-separated arguments to pass to the original command.

How the wrapper launches your application

The wrapper fork+execs CLOUDTASER_ORIGINAL_CMD with CLOUDTASER_ORIGINAL_ARGS. Secrets are injected into the child process's environment via BuildChildEnv -> execve(). The wrapper remains as PID 1 (parent process) for lease renewal, signal forwarding, secret rotation, and eBPF agent communication. The wrapper's own /proc/1/environ is structurally clean -- secrets are fetched from vault into Go heap memory only after the wrapper has already exec'd, so the kernel's frozen env_start..env_end snapshot for PID 1 never contained them. The child's /proc/<child>/environ does contain the secrets (that is how the application reads them); cross-process reads of the child's environ are blocked by the eBPF kprobe on openat for all monitored PIDs.


Rotation

CLOUDTASER_ROTATION

Required No
Default restart
Values restart, sighup, none

Controls behaviour when OpenBao secrets are updated during the lease or renewal cycle.

Value Behaviour
restart Terminate the application and re-launch with new secrets
sighup Send SIGHUP to the application process
none No rotation; secrets are fetched once at startup

RENEWAL_INTERVAL

Required No
Default 30s
Example 30s, 5m, 1h

How often the wrapper checks and renews OpenBao leases. Uses Go duration format.


eBPF Integration

CLOUDTASER_EBPF_AGENT_ADDR

Required No
Default --
Example unix:///var/run/cloudtaser/ebpf.sock

The gRPC address of the eBPF agent running on the node. When set, the wrapper registers with the eBPF agent at startup to enable kernel-level secret protection for the application process.

CLOUDTASER_POD_UID

Required No
Default --

The Kubernetes pod UID, used by the eBPF agent to identify the pod's cgroup. Automatically set by the webhook via the downward API.


Health and Observability

CLOUDTASER_HEALTH_ADDR

Required No
Default :8199
Example :8199, 0.0.0.0:9090
Legacy alias HEALTH_ADDR (still accepted)

The address for the wrapper's health check TLS endpoint. The webhook configures liveness and readiness probes to hit this address. The health server starts with TLS from the first listener bind (using an ephemeral self-signed certificate until the operator delivers the broker-minted certificate via /v1/unseal).

Health endpoints

Endpoint Method Description
/healthz GET Returns 200 OK when the wrapper PID 1 is alive. Always returns 200 regardless of sealed/unsealed/secret-not-provisioned state -- the wrapper is alive in all of these states.
/readyz GET Returns 200 OK when the application process has been launched and is running. Returns 503 when sealed (waiting for unseal token), when secrets have not been provisioned yet ({"reason":"secret_not_provisioned"}), or when the child process is not alive.
/v1/pubkey GET Returns the wrapper's ephemeral X25519 ECDH public key for encrypted unseal.
/v1/unseal POST Accepts the vault token (ECDH-encrypted or plaintext JSON) to unseal the wrapper. Authenticated via JWT + optional mTLS.
/v1/status GET Returns current sealed/unsealed and ready status.

/readyz response states

State HTTP Status Response body
Sealed (waiting for token) 503 {"ready":false,"sealed":true}
Secret not provisioned 503 {"ready":false,"reason":"secret_not_provisioned"}
Ready, child alive 200 {"ready":true,"child_alive":true,"sealed":false}
Ready, child dead 503 {"ready":true,"child_alive":false,"sealed":false}

Secret-not-provisioned behaviour (wrapper v0.2.12+)

When the upstream secret has not been provisioned in vault yet, the wrapper stays alive indefinitely instead of crash-looping. /healthz continues returning 200 (preventing kubelet Liveness-driven restarts and CrashLoopBackOff), while /readyz returns 503 with reason=secret_not_provisioned. The wrapper retries with bounded exponential backoff (5s base, doubling to 60s cap, +25% jitter) until the secret appears. Only configuration errors (auth failure, invalid vault path) cause os.Exit.

Health server hardening defaults

The health server applies the following timeouts and limits to protect against slowloris, amplification, and brute-force attacks:

Setting Value Purpose
ReadHeaderTimeout 5s Prevents slow-header connection holding
ReadTimeout 30s Outer bound on full request read
WriteTimeout 30s Prevents slow-consumer connection holding
IdleTimeout 120s Closes idle keepalive connections
MaxHeaderBytes 64 KB Rejects oversized headers
Unseal body limit 64 KB LimitReader on /v1/unseal body
Unseal body read deadline 10s Inner bound on body read (prevents slowloris-style byte-at-a-time attacks)

Unseal rate limiting

The /v1/unseal endpoint is rate-limited to prevent brute-force token guessing and denial-of-service by co-located attackers:

Limit Value Purpose
Per-IP rate 100 req/min (burst 10) Prevents a single attacker from exhausting the budget
Global rate 1000 req/min (burst 50) Prevents fan-out from many IPs exhausting goroutines
Stale entry cleanup 5 minutes Per-IP entries not seen in 5 minutes are lazily removed

Rate limiting is applied AFTER JWT authentication -- an unauthenticated attacker cannot exhaust the rate limit budget and lock out the legitimate operator.


Memory Protection

CLOUDTASER_REQUIRE_MLOCK

Required No
Default true
Values true, false

When true, the wrapper calls mlockall() to prevent secret-containing memory pages from being swapped to disk. If the mlock syscall fails (e.g., due to missing CAP_IPC_LOCK), the wrapper exits with an error rather than running without memory protection.

Requires CAP_IPC_LOCK

The pod's security context must allow CAP_IPC_LOCK, or the container runtime must set appropriate ulimit values. The operator automatically adds the required capability when injecting the wrapper.

CLOUDTASER_REQUIRE_MEMFD_SECRET

Required No
Default false
Values true, false

When true, the wrapper stores secrets in memory regions created with memfd_secret(), which are invisible to the kernel and cannot be read via /proc/pid/mem. Requires Linux kernel 5.14 or later.

Kernel requirement

memfd_secret() is available on Linux 5.14+. Most managed Kubernetes services (GKE, EKS, AKS) run kernels that support this syscall. See Kernel Compatibility for details.


Broker TLS (Platform Integration)

CLOUDTASER_BROKER_TLS_CERT

Required No
Default --

TLS client certificate for connecting to the cloudtaser Platform broker -- either an inline PEM block or a filesystem path. The operator (v0.2.0+) injects PEM content directly; older operators injected file paths. Both forms are accepted.

CLOUDTASER_BROKER_TLS_KEY

Required No
Default --

TLS client key -- either an inline PEM block or a filesystem path.

CLOUDTASER_BROKER_TLS_CA

Required No
Default --

CA certificate for verifying the Platform broker's TLS certificate -- either an inline PEM block or a filesystem path.


Complete Reference Table

Variable Required Default Description
VAULT_ADDR Yes -- OpenBao server address
VAULT_TOKEN Conditional -- Static OpenBao token
VAULT_AUTH_METHOD No kubernetes Auth method
VAULT_AUTH_ROLE Conditional -- K8s auth role
VAULT_AUTH_MOUNT_PATH No auth/kubernetes Auth mount path
VAULT_SKIP_VERIFY No false Skip TLS verification
CLOUDTASER_SECRET_PATHS Yes -- OpenBao KV paths
CLOUDTASER_ENV_MAP No -- Field-to-env mappings
CLOUDTASER_ORIGINAL_CMD Yes -- Application command
CLOUDTASER_ORIGINAL_ARGS No -- Application arguments
CLOUDTASER_ROTATION No restart Rotation strategy
RENEWAL_INTERVAL No 5m Token renewal interval
CLOUDTASER_EBPF_AGENT_ADDR No -- eBPF agent gRPC address
CLOUDTASER_POD_UID No -- Pod UID for eBPF
CLOUDTASER_HEALTH_ADDR No :8199 Health endpoint address (legacy alias: HEALTH_ADDR)
CLOUDTASER_REQUIRE_MLOCK No true Require mlock
CLOUDTASER_REQUIRE_MEMFD_SECRET No false Require memfd_secret
CLOUDTASER_BROKER_TLS_CERT No -- Platform TLS cert
CLOUDTASER_BROKER_TLS_KEY No -- Platform TLS key
CLOUDTASER_BROKER_TLS_CA No -- Platform TLS CA

Systemd Usage Example

For protecting systemd services outside of Kubernetes:

/etc/systemd/system/myapp.service
[Unit]
Description=My Application (cloudtaser protected)
After=network-online.target
Wants=network-online.target

[Service]
Type=exec
Environment=VAULT_ADDR=https://vault.eu-west-1.example.com:8200
Environment=VAULT_AUTH_METHOD=token
Environment=VAULT_TOKEN_FILE=/run/secrets/vault-token
Environment=CLOUDTASER_SECRET_PATHS=secret/data/myapp/config
Environment=CLOUDTASER_ENV_MAP=db_password=DB_PASS,api_key=API_KEY
Environment=CLOUDTASER_ORIGINAL_CMD=/usr/bin/myapp
Environment=CLOUDTASER_ORIGINAL_ARGS=--config,/etc/myapp/config.yaml
Environment=CLOUDTASER_REQUIRE_MLOCK=true
Environment=CLOUDTASER_ROTATION=none
Environment=CLOUDTASER_HEALTH_ADDR=:8199
ExecStart=/usr/local/bin/cloudtaser-wrapper
Restart=on-failure
LimitMEMLOCK=infinity

[Install]
WantedBy=multi-user.target

LimitMEMLOCK

Set LimitMEMLOCK=infinity in the systemd unit to allow the wrapper to call mlockall(). Without this, the wrapper will fail to start when CLOUDTASER_REQUIRE_MLOCK=true.