titan-iac/services/vault/scripts/vault_k8s_auth_configure.sh
jenkins 83f8d67640
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
feat(hermes): take the Anthropic credential from Vault
The Claude subscription OAuth token was created as a manual kubectl Secret in
the interest of demo time, with migration to Vault agreed as follow-up. The
value now lives at kv/atlas/hermes/agent-tokens and is injected as a file.

The hermes role gains that path and binds the hermes-triage service account
the deployment actually runs as; it previously bound only hermes-vault. The
init container prefers the Vault file and falls back to the Secret, so this
can be rolled back by removing the annotations alone, and the Secret should be
deleted once Vault has been serving it for a while.

Vault was reachable all along without the operator credential: Ariadne already
holds a vault-admin Kubernetes auth role, which is how the value was written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 12:14:52 -03:00

317 lines
11 KiB
Bash

#!/usr/bin/env sh
set -eu
log() { echo "[vault-k8s-auth] $*"; }
vault_cmd() {
for attempt in 1 2 3 4 5 6; do
set +e
output="$(vault "$@" 2>&1)"
status=$?
set -e
if [ "${status}" -eq 0 ]; then
printf '%s' "${output}"
return 0
fi
log "vault command failed; retrying (${attempt}/6)"
sleep $((attempt * 2))
done
log "vault command failed; giving up"
return 1
}
ensure_token() {
if [ -n "${VAULT_TOKEN:-}" ]; then
return
fi
role="${VAULT_K8S_ROLE:-vault}"
jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
if ! VAULT_TOKEN="$(vault_cmd write -field=token auth/kubernetes/login role="${role}" jwt="${jwt}")"; then
log "kubernetes auth login failed; set VAULT_TOKEN or fix role ${role}"
exit 1
fi
export VAULT_TOKEN
}
if ! status_json="$(vault_cmd status -format=json)"; then
log "vault status failed; check VAULT_ADDR and VAULT_TOKEN"
exit 1
fi
if ! printf '%s' "${status_json}" | grep -q '"initialized":[[:space:]]*true'; then
log "vault not initialized; skipping"
exit 0
fi
if printf '%s' "${status_json}" | grep -q '"sealed":[[:space:]]*true'; then
log "vault sealed; skipping"
exit 0
fi
ensure_token
k8s_host="https://${KUBERNETES_SERVICE_HOST}:443"
k8s_ca="$(cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt)"
k8s_token="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
role_ttl="${VAULT_K8S_ROLE_TTL:-1h}"
token_reviewer_jwt="${VAULT_K8S_TOKEN_REVIEWER_JWT:-}"
if [ -z "${token_reviewer_jwt}" ] && [ -n "${VAULT_K8S_TOKEN_REVIEWER_JWT_FILE:-}" ] && [ -r "${VAULT_K8S_TOKEN_REVIEWER_JWT_FILE}" ]; then
token_reviewer_jwt="$(cat "${VAULT_K8S_TOKEN_REVIEWER_JWT_FILE}")"
fi
if [ -z "${token_reviewer_jwt}" ]; then
token_reviewer_jwt="${k8s_token}"
fi
if ! vault_cmd auth list -format=json | grep -q '"kubernetes/"'; then
log "enabling kubernetes auth"
vault_cmd auth enable kubernetes
fi
log "configuring kubernetes auth"
vault_cmd write auth/kubernetes/config \
token_reviewer_jwt="${token_reviewer_jwt}" \
kubernetes_host="${k8s_host}" \
kubernetes_ca_cert="${k8s_ca}"
write_raw_policy() {
name="$1"
body="$2"
log "writing policy ${name}"
printf '%s\n' "${body}" | vault_cmd policy write "${name}" -
}
write_policy_and_role() {
role="$1"
namespace="$2"
service_accounts="$3"
read_paths="$4"
write_paths="$5"
extra_rules="${6:-}"
policy_body=""
for path in ${read_paths}; do
policy_body="${policy_body}
path \"kv/data/atlas/${path}\" {
capabilities = [\"read\"]
}
path \"kv/metadata/atlas/${path}\" {
capabilities = [\"list\"]
}
"
done
for path in ${write_paths}; do
policy_body="${policy_body}
path \"kv/data/atlas/${path}\" {
capabilities = [\"create\", \"update\", \"read\"]
}
path \"kv/metadata/atlas/${path}\" {
capabilities = [\"list\"]
}
"
done
if [ -n "${extra_rules}" ]; then
policy_body="${policy_body}
${extra_rules}
"
fi
log "writing policy ${role}"
printf '%s\n' "${policy_body}" | vault_cmd policy write "${role}" -
log "writing role ${role}"
vault_cmd write "auth/kubernetes/role/${role}" \
bound_service_account_names="${service_accounts}" \
bound_service_account_namespaces="${namespace}" \
policies="${role}" \
ttl="${role_ttl}"
}
vault_admin_policy='
path "sys/auth" {
capabilities = ["read"]
}
path "sys/auth/*" {
capabilities = ["create", "update", "delete", "sudo", "read"]
}
path "auth/kubernetes/*" {
capabilities = ["create", "update", "read"]
}
path "auth/oidc/*" {
capabilities = ["create", "update", "read"]
}
path "sys/policies/acl" {
capabilities = ["list"]
}
path "sys/policies/acl/*" {
capabilities = ["create", "update", "read"]
}
path "sys/internal/ui/mounts" {
capabilities = ["read"]
}
path "sys/internal/ui/mounts/*" {
capabilities = ["read"]
}
path "sys/mounts" {
capabilities = ["read"]
}
path "sys/mounts/auth/*" {
capabilities = ["read", "update", "sudo"]
}
path "sys/storage/raft/configuration" {
capabilities = ["read"]
}
path "sys/storage/raft/snapshot" {
capabilities = ["read"]
}
path "kv/data/atlas/vault/*" {
capabilities = ["read"]
}
path "kv/metadata/atlas/vault/*" {
capabilities = ["list"]
}
path "kv/data/*" {
capabilities = ["create", "update", "read", "delete", "patch"]
}
path "kv/metadata" {
capabilities = ["list"]
}
path "kv/metadata/*" {
capabilities = ["read", "list", "delete"]
}
path "kv/data/atlas/shared/*" {
capabilities = ["create", "update", "read", "patch"]
}
path "kv/metadata/atlas/shared/*" {
capabilities = ["list"]
}
'
write_raw_policy "vault-admin" "${vault_admin_policy}"
dev_kv_policy='
path "sys/internal/ui/mounts" {
capabilities = ["read"]
}
path "sys/internal/ui/mounts/*" {
capabilities = ["read"]
}
path "kv/metadata" {
capabilities = ["list"]
}
path "kv/metadata/atlas" {
capabilities = ["list"]
}
path "kv/metadata/atlas/shared" {
capabilities = ["list"]
}
path "kv/metadata/atlas/shared/*" {
capabilities = ["list"]
}
path "kv/data/atlas/shared/*" {
capabilities = ["read"]
}
'
write_raw_policy "dev-kv" "${dev_kv_policy}"
log "writing role vault-admin"
vault_cmd write "auth/kubernetes/role/vault-admin" \
bound_service_account_names="vault-admin,ariadne" \
bound_service_account_namespaces="vault,maintenance" \
policies="vault-admin" \
ttl="${role_ttl}"
write_policy_and_role "outline" "outline" "outline-vault" \
"outline/* shared/postmark-relay" ""
write_policy_and_role "planka" "planka" "planka-vault" \
"planka/* shared/postmark-relay" ""
write_policy_and_role "bstein-dev-home" "bstein-dev-home" "bstein-dev-home,bstein-dev-home-vault-sync" \
"portal/* shared/chat-ai-keys-runtime shared/portal-e2e-client shared/postmark-relay mailu/mailu-initial-account-secret shared/harbor-pull" ""
write_policy_and_role "gitea" "gitea" "gitea-vault" \
"gitea/*" ""
write_policy_and_role "vaultwarden" "vaultwarden" "vaultwarden-vault" \
"vaultwarden/* mailu/mailu-initial-account-secret" ""
write_policy_and_role "sso" "sso" "sso-vault,sso-vault-sync,mas-secrets-ensure" \
"sso/* portal/bstein-dev-home-keycloak-admin shared/keycloak-admin shared/portal-e2e-client shared/postmark-relay shared/harbor-pull" ""
write_policy_and_role "mailu-mailserver" "mailu-mailserver" "mailu-vault-sync" \
"mailu/* shared/postmark-relay shared/harbor-pull" ""
write_policy_and_role "harbor" "harbor" "harbor-vault-sync" \
"harbor/* shared/harbor-pull" ""
write_policy_and_role "nextcloud" "nextcloud" "nextcloud-vault" \
"nextcloud/* shared/keycloak-admin shared/postmark-relay" ""
write_policy_and_role "comms" "comms" "comms-vault,atlasbot" \
"comms/* shared/chat-ai-keys-runtime shared/harbor-pull" ""
write_policy_and_role "jenkins" "jenkins" "jenkins,jenkins-vault-sync" \
"jenkins/* shared/harbor-pull quality/sonarqube-oidc" ""
write_policy_and_role "monitoring" "monitoring" "monitoring-vault-sync" \
"monitoring/* shared/postmark-relay shared/harbor-pull" ""
write_policy_and_role "logging" "logging" "logging-vault-sync" \
"logging/* shared/harbor-pull" ""
write_policy_and_role "pegasus" "jellyfin" "pegasus-vault-sync" \
"pegasus/* shared/harbor-pull" ""
write_policy_and_role "crypto" "crypto" "crypto-vault-sync" \
"crypto/* shared/harbor-pull" ""
write_policy_and_role "health" "health" "health-vault-sync" \
"health/*" ""
write_policy_and_role "game-stream" "game-stream" "game-stream-vault" \
"game-stream/*" ""
write_policy_and_role "hermes" "hermes" "hermes-vault,hermes-triage" \
"hermes/operator-oidc hermes/agent-tokens" ""
write_policy_and_role "veles" "veles" "veles-backend,veles-generator,veles-postgres,veles-vault-sync" \
"veles/* shared/harbor-pull shared/postmark-relay" ""
write_policy_and_role "veles-sim" "veles" "veles-sim" \
"veles/codex-cli" ""
write_policy_and_role "veles-secrets" "veles" "veles-secrets-ensure" \
"shared/postmark-relay" \
"veles/*"
write_policy_and_role "cassandra" "cassandra" "cassandra-backend,cassandra-generator,cassandra-postgres,cassandra-vault-sync" \
"cassandra/* shared/harbor-pull shared/postmark-relay" ""
write_policy_and_role "cassandra-sim" "cassandra" "cassandra-sim" \
"cassandra/codex-cli" ""
write_policy_and_role "cassandra-secrets" "cassandra" "cassandra-secrets-ensure" \
"shared/postmark-relay veles/app-secrets" \
"cassandra/*"
write_policy_and_role "cassandra-migration" "cassandra" "cassandra-db-migration" \
"veles/veles-db cassandra/cassandra-db" ""
write_policy_and_role "maintenance" "maintenance" "ariadne,maintenance-vault-sync,metis" \
"maintenance/ariadne-db maintenance/metis-oidc maintenance/soteria-oidc maintenance/metis-ssh-keys maintenance/metis-runtime portal/atlas-portal-db portal/bstein-dev-home-keycloak-admin mailu/mailu-db-secret mailu/mailu-initial-account-secret nextcloud/nextcloud-db nextcloud/nextcloud-admin health/wger-admin finance/firefly-secrets comms/mas-admin-client-runtime comms/atlasbot-credentials-runtime comms/synapse-db comms/synapse-admin vault/vault-oidc-config shared/harbor-pull shared/soteria-restic harbor/harbor-core" "" \
'
path "kv/data/atlas/nodes/*" {
capabilities = ["read"]
}
path "kv/metadata/atlas/nodes/*" {
capabilities = ["list"]
}
'
write_policy_and_role "maintenance-metis-token-sync" "maintenance" "metis-token-sync" \
"" \
"maintenance/metis-runtime"
write_policy_and_role "finance" "finance" "finance-vault" \
"finance/* shared/postmark-relay" ""
write_policy_and_role "finance-secrets" "finance" "finance-secrets-ensure" \
"" \
"finance/*"
write_policy_and_role "longhorn" "longhorn-system" "longhorn-vault,longhorn-vault-sync" \
"longhorn/* shared/harbor-pull" ""
write_policy_and_role "postgres" "postgres" "postgres-vault" \
"postgres/postgres-db" ""
write_policy_and_role "vault" "vault" "vault" \
"vault/*" ""
write_policy_and_role "sso-secrets" "sso" "mas-secrets-ensure" \
"shared/keycloak-admin shared/postmark-relay maintenance/metis-ssh-keys" \
"harbor/harbor-oidc vault/vault-oidc-config comms/synapse-oidc logging/oauth2-proxy-logs-oidc finance/actual-oidc maintenance/metis-oidc maintenance/soteria-oidc maintenance/metis-ssh-keys veles/veles-oidc cassandra/cassandra-oidc gitea/gitea-veles-oidc gitea/gitea-cassandra-oidc hermes/operator-oidc" \
'
path "kv/data/atlas/nodes/*" {
capabilities = ["create", "update", "read"]
}
path "kv/metadata/atlas/nodes/*" {
capabilities = ["list"]
}
'
write_policy_and_role "crypto-secrets" "crypto" "crypto-secrets-ensure" \
"" \
"crypto/wallet-monero-temp-rpc-auth"
write_policy_and_role "comms-secrets" "comms" \
"comms-secrets-ensure,mas-db-ensure,mas-admin-client-secret-writer,othrys-synapse-signingkey-job" \
"" \
"comms/turn-shared-secret comms/livekit-api comms/synapse-redis comms/synapse-macaroon comms/atlasbot-credentials-runtime comms/synapse-db comms/synapse-admin comms/synapse-registration comms/mas-db comms/mas-admin-client-runtime comms/mas-secrets-runtime comms/othrys-synapse-signingkey"