Downward API integration

Kubernetes Downward API integration for the LinuxGuard agent — LINUXGUARD_NODE_NAME and LINUXGUARD_POD_UID via fieldRef, workload identity derivation.

The LinuxGuard agent derives its workload identifier in ephemeral mode from two Kubernetes Downward API values — the node name and the pod UID. This page documents the binding, the workload-id derivation rule, and the consequences of getting the binding wrong.

Important: The agent refuses to start in ephemeral mode when neither (a) both LINUXGUARD_NODE_NAME AND LINUXGUARD_POD_UID are set, NOR (b) --workload-id <hex> is supplied on the command line. There is no silent UUID fallback. The hard-error message lists all three input sources so the operator can diagnose without reading code.

Required env-var bindings

Two env vars are sourced from the Kubernetes Downward API via valueFrom.fieldRef. Both MUST be present in the Pod spec for ephemeral mode to start; missing either one produces the workload-id derivation error.

env:
  - name: LINUXGUARD_NODE_NAME
    valueFrom:
      fieldRef:
        fieldPath: spec.nodeName
  - name: LINUXGUARD_POD_UID
    valueFrom:
      fieldRef:
        fieldPath: metadata.uid

Env var

fieldRef.fieldPath

What it resolves to

LINUXGUARD_NODE_NAME

spec.nodeName

The name of the node the Pod is scheduled on (e.g., ip-10-0-0-42, worker-node-3, gke-default-pool-abc123-xyz). Kubelet sets this when the Pod is admitted; it never changes for the lifetime of the Pod.

LINUXGUARD_POD_UID

metadata.uid

The RFC 4122 v4 UUID Kubernetes assigns to the Pod (e.g., 7b8f9c1d-3e4a-4b5c-9d6e-1f2a3b4c5d6e). Stable for the lifetime of the Pod; a restart with the same Pod name produces a NEW uid.

Both fields are populated at Pod admission time by the Kubernetes API server. No apiserver lookups occur from inside the container — the values are baked into the container's environ block at scheduling time, and the agent reads them with os.Getenv at startup.

Workload identifier derivation

The agent uses the two env vars to derive its workload identifier. The derivation is deterministic:

The colon separator distinguishes the two-part hash from a malicious value that crafts a node name containing a UID-looking suffix (or vice versa). The hex-encoded SHA-256 is used as the Idempotency-Key HTTP header on the enrolment POST so a retried request does not produce two server identities for the same workload.

The derivation order is:

  1. Explicit --workload-id <hex>sha256(explicit). The flag value is hashed (not used verbatim).

  2. Downward API env varssha256(LINUXGUARD_NODE_NAME + ":" + LINUXGUARD_POD_UID). Both must be present.

  3. Neither set → hard error:

This deliberate hard-error matters: a misconfigured Pod that derived a random UUID at startup would silently enrol a new identity on every restart, producing a sprawl of orphan enrollments in the tenant. The explicit failure forces the operator to fix the manifest before the cluster's agent fleet accumulates phantom identities.

Stability properties

Event

LINUXGUARD_NODE_NAME changes?

LINUXGUARD_POD_UID changes?

Workload-id changes?

Pod restarts on the same node (e.g., kubelet restart, container OOM)

No

No (same Pod object)

No

Pod evicted, rescheduled to the same node

No

Yes (new Pod object → new uid)

Yes

Pod evicted, rescheduled to a different node

Yes

Yes

Yes

DaemonSet rolled (new revision)

No

Yes (new Pod per node)

Yes

Container restarts within the same Pod (e.g., crash loop)

No

No

No

For the DaemonSet shape, this means every DaemonSet roll produces a new workload identity per node. The agent re-enrols on each roll because the workload-id has changed. This is the intended behavior — a freshly-deployed agent gets a fresh server identity rather than inheriting state from the prior pod.

For the sidecar shape, the workload-id changes whenever the application Pod is rescheduled. Sidecars in a Deployment get a new uid on every Pod replacement; sidecars in a StatefulSet get a stable uid only when the Pod is recreated under the same name (which still produces a new uid in Kubernetes' model).

Cross-reference with TLS cache

The TLS cert cache (--tls-cache, see Ephemeral mode § TLS cache restart semantics) keys its invalidation tag on the workload-id:

This means a workload-id change (e.g., Pod evicted and rescheduled) automatically invalidates the cached cert chain, and the agent performs a fresh enrolment under the new identity. Operators do not need to manually clear the cache on reschedule.

Security context

The Downward API binding itself has no additional security context implications beyond the base ephemeral mode security context — see Distroless image § Security context. Both env vars are public Kubernetes metadata (node names and Pod UIDs appear in kubectl get pods -o wide output) and do not require Secret-style protection.

The LINUXGUARD_POD_UID env var is exposed in /proc/<pid>/environ for the agent's lifetime — this is acceptable because the Pod UID is not a secret. The token (LINUXGUARD_ENROLL_TOKEN) IS a secret and receives the os.Unsetenv scrub at startup; the Downward API env vars do NOT receive that treatment and remain readable for the agent's lifetime.

Host paths

The Downward API binding requires no additional host-path mounts beyond those documented in the per-orchestrator spoke (Kubernetes DaemonSet § Host paths or Ephemeral mode § Host paths). The fieldRef values are baked into the container's environ block at admission time — no node-level filesystem access is involved.

Pod Security Standard compatibility

PSS profile

Downward API fieldRef compatible?

Notes

privileged

Yes

No PSS-level constraints.

baseline

Yes

The Downward API fieldRef mechanism is permitted under baseline; the binding involves no privileged operations.

restricted

Yes

The Downward API fieldRef mechanism is permitted under restricted; the binding involves no privileged operations.

The Downward API binding is PSS-neutral. The agent's other capability requirements (CAP_BPF, CAP_PERFMON, hostPID) are what classify the Pod spec as privileged — not the Downward API itself. A future PSS-compatible deployment shape would inherit the same Downward API binding unchanged.

RBAC

The Downward API fieldRef mechanism requires no RBAC — the values are populated by the kubelet at Pod admission time, not by an apiserver call from inside the container. The Pod's ServiceAccount needs no Role or ClusterRole to read spec.nodeName or metadata.uid because the agent does not read them via the apiserver; it reads them from os.Environ.

This contrasts with the Projected ServiceAccount Token pattern (where the kubelet projects a token into a volume) — that pattern DOES require a workload-identity-binding RBAC, but the LinuxGuard agent does not use it.

Common mistakes

Hardcoding the node name in a ConfigMap

This populates the same node name on every pod of the DaemonSet. The workload-id collapses to a single value across the cluster, every agent enrols under the same identity, and the backend de-duplicates them — silently dropping coverage of every node except the first.

Correct: Always use valueFrom.fieldRef.fieldPath: spec.nodeName.

Hardcoding a UUID

The same problem — the workload-id derives the same value across all pods. Every pod enrols under the same identity; coverage collapses to one phantom workload.

Correct: Always use valueFrom.fieldRef.fieldPath: metadata.uid.

Using metadata.name instead of metadata.uid

A Pod's name is not the same as its uid. In a DaemonSet, names follow the pattern <daemonset-name>-<hash> and DO vary per pod; this misconfiguration may appear to work in a fresh deployment but produces collisions whenever a Pod is recreated under the same name (StatefulSet recreations under the same ordinal, for example).

Correct: Use metadata.uid — the only field guaranteed unique across the entire cluster lifetime.

Forgetting one of the two env vars

The agent refuses to start with the hard-error message above. There is no fallback to "node-name-only" workload identity by design.

Correct: Always set BOTH env vars together, OR supply --workload-id <hex> on the command line as the explicit override.

Non-Kubernetes ephemeral deployment

In Docker / Podman ephemeral deployments (no Kubernetes), spec.nodeName and metadata.uid do not exist. The Downward API binding is Kubernetes-specific.

Correct: Supply both env vars manually via -e LINUXGUARD_NODE_NAME=<hostname> -e LINUXGUARD_POD_UID=<uuid>, OR pass --workload-id <hex> as the explicit identifier. See Ephemeral mode § Example: Docker run.

Verification

After applying a manifest that uses the Downward API binding, verify the env vars are populated correctly:

The output should show the derived workload-id and the node name. If show-config reports a missing workload-id, the most common causes are:

  1. The Pod was not scheduled with the Downward API env block in its spec — recheck kubectl -n linuxguard get pod $POD -o yaml | grep -A 3 LINUXGUARD_NODE_NAME.

  2. A fieldRef.fieldPath is wrong — confirm it is exactly spec.nodeName for the node and metadata.uid for the Pod UID.

  3. The values were overridden by a ConfigMap or envFrom block that took precedence — verify env-var precedence with kubectl exec $POD -- env | grep LINUXGUARD.


Next Step: Enrollment tokens →

Related: Container deployment hub | Ephemeral mode | Kubernetes DaemonSet | Environment variables | start CLI reference

Last updated

Was this helpful?