homelab ha token: dedicated openclaw/ha-tokens secret + least-priv RBAC for emo
Some checks are pending
Build infra CLI / build (push) Waiting to run
ci/woodpecker/push/default Pipeline was successful

`ha token` originally read openclaw/openclaw-secrets -> skill_secrets, which only
cluster admins can read — so it hung/failed for the non-admin operator it was
built for (emo = emil.barzin@gmail.com, OIDC group "Home Server Admins", whose
identity is deliberately barred from secrets in the openclaw namespace).

Split the HA tokens into a dedicated secret openclaw/ha-tokens (keys sofia/london)
with a Role + RoleBinding granting `get` on JUST that secret to the Home Server
Admins group (k8s RBAC can't scope to a JSON sub-key, hence a separate object).
emo now resolves the HA token with their own identity, WITHOUT gaining the rest
of skill_secrets (slack_webhook, uptime_kuma_password). openclaw's own deployment
keeps reading openclaw-secrets — purely additive.

- stacks/openclaw/ha_tokens.tf: new secret + least-privilege Role/RoleBinding
- cli/cmd_ha.go: read openclaw/ha-tokens (raw base64 per-instance key); drop JSON parse
- README + ADR-0012 updated; VERSION -> v0.7.1

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-06-21 10:45:32 +00:00
parent a091689603
commit b1bbe42821
6 changed files with 100 additions and 51 deletions

View file

@ -0,0 +1,52 @@
# Dedicated secret holding ONLY the Home Assistant API tokens, split out of
# openclaw-secrets so the `homelab ha token` CLI verb can serve non-admin
# operators (emo = emil.barzin@gmail.com, group "Home Server Admins") WITHOUT
# granting them read on the full skill_secrets blob (which also carries
# slack_webhook + uptime_kuma_password). openclaw's own deployment keeps reading
# openclaw-secrets this is purely an additive, least-privilege carve-out for
# the CLI. See infra/cli/cmd_ha.go + docs/adr/0012.
resource "kubernetes_secret" "ha_tokens" {
metadata {
name = "ha-tokens"
namespace = kubernetes_namespace.openclaw.metadata[0].name
}
data = {
# keys match the homelab `ha token --instance <sofia|london>` mapping
sofia = local.skill_secrets["home_assistant_sofia_token"]
london = local.skill_secrets["home_assistant_token"]
}
type = "Opaque"
}
# get on JUST the ha-tokens secret (resource_names pins it to this one object),
# bound to the "Home Server Admins" OIDC group the group emo authenticates
# into. Scope deliberately excludes openclaw-secrets and every other secret.
resource "kubernetes_role" "ha_tokens_reader" {
metadata {
name = "ha-tokens-reader"
namespace = kubernetes_namespace.openclaw.metadata[0].name
}
rule {
api_groups = [""]
resources = ["secrets"]
resource_names = [kubernetes_secret.ha_tokens.metadata[0].name]
verbs = ["get"]
}
}
resource "kubernetes_role_binding" "ha_tokens_reader" {
metadata {
name = "ha-tokens-reader"
namespace = kubernetes_namespace.openclaw.metadata[0].name
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "Role"
name = kubernetes_role.ha_tokens_reader.metadata[0].name
}
subject {
kind = "Group"
name = "Home Server Admins"
api_group = "rbac.authorization.k8s.io"
}
}