Skip to content

The security model

Explanation — what the operator is allowed to do, and where the boundary between operator and workload runs. Nothing here needs a cluster.

The operator is a high-privilege component: it reconciles custom resources across namespaces and holds credentials for the Home Assistant instances it manages. The design answer is not to make it less capable, but to keep the blast radius explicit — the operator's own workload is locked down hard, while the Home Assistant pods it manages are deliberately left alone, because they legitimately need privileges the operator never does.

What the operator's own namespace enforces

The operator's own namespace can carry Pod Security Admission labels — but only when the chart owns it (namespace.create=true). That is not the default: a namespace you create yourself, or one Helm creates with --create-namespace, carries no such labels and enforces nothing. The labels the chart applies are:

pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest

The controller-manager pod already satisfies restricted:

  • runs as a non-root user (runAsNonRoot: true),
  • seccompProfile: RuntimeDefault,
  • allowPrivilegeEscalation: false,
  • all Linux capabilities dropped (capabilities.drop: ["ALL"]),
  • no host namespaces, hostPath volumes, or host ports.

Version latest means the namespace always applies the newest restricted rules and automatically tightens on cluster upgrades.

The operator pod satisfies restricted either way — the labels decide whether the cluster enforces it, not whether the operator complies. See enforce Pod Security Standards for turning enforcement on.

Why Home Assistant pods are out of scope

Home Assistant pods are out of scope

This enforcement applies only to the operator's own workloads. Home Assistant pods run in their own namespaces and are deliberately not placed under restricted. Many Home Assistant setups need elevated privileges (for example hostNetwork, or access to USB/Zigbee devices), which restricted would block.

Clusters without Pod Security Admission

Pod Security Admission is a cluster feature. On clusters where it is disabled (or that predate it), the labels are inert — they never block installation. The pod's securityContext remains compliant, so enforcement takes effect immediately once PSA is enabled.

How wide the operator's permissions are

By default the operator watches every namespace in the cluster, which needs a cluster-wide role binding. That is the widest permission it can hold, and it is being phased out: naming the namespaces you actually use in watchNamespaces switches it to per-namespace bindings instead.

The default is wide because it is the one that works without you knowing in advance where Home Assistant will live. The trade-off is real, so the install warns about it rather than staying quiet — see install the operator.

One permission the operator deliberately does not hold is pods/exec. It would be the obvious way to fix things inside a running Home Assistant container, and it is also a permission that lets its holder run anything, as anyone, in any pod it can reach. Where the operator needs to change something inside the container — clearing an IP ban, for instance — it restarts the pod with an init-container instead. That is clumsier, and it keeps the permission off the list. See IP ban self-recovery.

Where the credentials live

Bootstrap creates a long-lived Home Assistant access token and stores it in a Kubernetes Secret named <ha-name>-homeassistant-api-token. Everything the operator does against Home Assistant's API uses that token, and it never leaves the cluster.

That Secret is worth treating as what it is: full control of the Home Assistant instance. Anyone who can read Secrets in that namespace can read it, which is one more reason to narrow watchNamespaces and keep Home Assistant in a namespace of its own.

Secrets you feed into Home Assistant work the other way round: the operator reads them and composes secrets.yaml, so the values end up on the instance's volume. They are as protected as that volume is, not more.

The admission webhook

The operator ships a validating admission webhook that rejects incoherent resources at kubectl apply time rather than letting them fail later. It runs with failurePolicy: Ignore by default, meaning that if the webhook is unavailable — during an operator rollout, say — resources are admitted unvalidated instead of being blocked.

That is the deliberate choice: this webhook exists to catch mistakes, not to be a security control, and an unavailable webhook that blocks every change would take your smart home down to prevent a typo.

See also