vault: provision Hermes LAN model gateway credentials
This commit is contained in:
parent
fc321d2fcc
commit
0274335dfe
@ -3,7 +3,7 @@
|
|||||||
apiVersion: batch/v1
|
apiVersion: batch/v1
|
||||||
kind: Job
|
kind: Job
|
||||||
metadata:
|
metadata:
|
||||||
name: vault-k8s-auth-hermes-11
|
name: vault-k8s-auth-hermes-12
|
||||||
namespace: vault
|
namespace: vault
|
||||||
spec:
|
spec:
|
||||||
backoffLimit: 2
|
backoffLimit: 2
|
||||||
|
|||||||
79
services/vault/hermes-model-gate-lan-token-seed-job.yaml
Normal file
79
services/vault/hermes-model-gate-lan-token-seed-job.yaml
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# services/vault/hermes-model-gate-lan-token-seed-job.yaml
|
||||||
|
# Purpose: create-only seed of the model-gate LAN API bearer token at
|
||||||
|
# kv/atlas/hermes/model-gate-lan-api (field: token). KV-v2 CAS create
|
||||||
|
# semantics -- an existing token is never overwritten. The script waits for
|
||||||
|
# the vault-k8s-auth-config CronJob (every 15 minutes) to write the seeder
|
||||||
|
# auth role, so a fresh merge does not fail the Job on the role race.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: hermes-model-gate-lan-token-seed
|
||||||
|
namespace: vault
|
||||||
|
---
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: vault-hermes-model-gate-lan-token-seed-1
|
||||||
|
namespace: vault
|
||||||
|
spec:
|
||||||
|
backoffLimit: 2
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
serviceAccountName: hermes-model-gate-lan-token-seed
|
||||||
|
enableServiceLinks: false
|
||||||
|
restartPolicy: Never
|
||||||
|
nodeSelector:
|
||||||
|
hardware: rpi5
|
||||||
|
kubernetes.io/arch: arm64
|
||||||
|
node-role.kubernetes.io/worker: "true"
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: kubernetes.io/hostname
|
||||||
|
operator: NotIn
|
||||||
|
values: [titan-04, titan-14, titan-18, titan-19, titan-24]
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 1000
|
||||||
|
fsGroupChangePolicy: OnRootMismatch
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: seed
|
||||||
|
image: docker.io/hashicorp/vault@sha256:4e33b126a59c0c333b76fb4e894722462659a6bec7c48c9ee8cea56fccfd2569
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: [sh, /scripts/vault_hermes_model_gate_lan_token_ensure.sh]
|
||||||
|
env:
|
||||||
|
- name: HOME
|
||||||
|
value: /tmp
|
||||||
|
- name: VAULT_ADDR
|
||||||
|
value: http://vault.vault.svc.cluster.local:8200
|
||||||
|
- name: VAULT_K8S_ROLE
|
||||||
|
value: hermes-model-gate-lan-token-seed
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop: ["ALL"]
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsGroup: 1000
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 100
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
volumeMounts:
|
||||||
|
- name: scripts
|
||||||
|
mountPath: /scripts
|
||||||
|
readOnly: true
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
|
resources:
|
||||||
|
requests: {cpu: 25m, memory: 32Mi}
|
||||||
|
limits: {cpu: 250m, memory: 128Mi}
|
||||||
|
volumes:
|
||||||
|
- name: scripts
|
||||||
|
configMap:
|
||||||
|
name: vault-hermes-model-gate-lan-token-seed-script
|
||||||
|
defaultMode: 0555
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
@ -12,6 +12,7 @@ resources:
|
|||||||
- statefulset.yaml
|
- statefulset.yaml
|
||||||
- k8s-auth-config-cronjob.yaml
|
- k8s-auth-config-cronjob.yaml
|
||||||
- hermes-auth-role-bootstrap-job.yaml
|
- hermes-auth-role-bootstrap-job.yaml
|
||||||
|
- hermes-model-gate-lan-token-seed-job.yaml
|
||||||
- oidc-config-cronjob.yaml
|
- oidc-config-cronjob.yaml
|
||||||
- service.yaml
|
- service.yaml
|
||||||
- certificate.yaml
|
- certificate.yaml
|
||||||
@ -27,6 +28,9 @@ configMapGenerator:
|
|||||||
- name: vault-k8s-auth-config-script
|
- name: vault-k8s-auth-config-script
|
||||||
files:
|
files:
|
||||||
- vault_k8s_auth_configure.sh=scripts/vault_k8s_auth_configure.sh
|
- vault_k8s_auth_configure.sh=scripts/vault_k8s_auth_configure.sh
|
||||||
|
- name: vault-hermes-model-gate-lan-token-seed-script
|
||||||
|
files:
|
||||||
|
- vault_hermes_model_gate_lan_token_ensure.sh=scripts/vault_hermes_model_gate_lan_token_ensure.sh
|
||||||
- name: vault-entrypoint
|
- name: vault-entrypoint
|
||||||
files:
|
files:
|
||||||
- vault-entrypoint.sh=scripts/vault-entrypoint.sh
|
- vault-entrypoint.sh=scripts/vault-entrypoint.sh
|
||||||
|
|||||||
@ -0,0 +1,177 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
secret_path="kv/atlas/hermes/model-gate-lan-api"
|
||||||
|
secret_api_path="kv/data/atlas/hermes/model-gate-lan-api"
|
||||||
|
jwt_file="${VAULT_K8S_JWT_FILE:-/var/run/secrets/kubernetes.io/serviceaccount/token}"
|
||||||
|
vault_role="${VAULT_K8S_ROLE:-hermes-model-gate-lan-token-seed}"
|
||||||
|
payload_file="${TMPDIR:-/tmp}/hermes-model-gate-lan-token.json"
|
||||||
|
|
||||||
|
log() { printf '[model-gate-lan-token] %s\n' "$*" >&2; }
|
||||||
|
cleanup() { rm -f "${payload_file}"; }
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
|
retry_command() {
|
||||||
|
attempt=1
|
||||||
|
while [ "${attempt}" -le 5 ]; do
|
||||||
|
set +e
|
||||||
|
command_output="$("$@" 2>&1)"
|
||||||
|
command_status=$?
|
||||||
|
set -e
|
||||||
|
if [ "${command_status}" -eq 0 ]; then
|
||||||
|
printf '%s' "${command_output}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ "${attempt}" -lt 5 ]; then
|
||||||
|
sleep "${attempt}"
|
||||||
|
fi
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
done
|
||||||
|
return "${command_status}"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_token() {
|
||||||
|
if [ -n "${VAULT_TOKEN:-}" ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
# The seeder auth role is written by the vault-k8s-auth-config CronJob
|
||||||
|
# (every 15 minutes), so a fresh merge can reach this Job before the role
|
||||||
|
# exists. Wait out that window instead of failing the Job.
|
||||||
|
attempt=1
|
||||||
|
while [ "${attempt}" -le 40 ]; do
|
||||||
|
jwt="$(cat "${jwt_file}")"
|
||||||
|
set +e
|
||||||
|
VAULT_TOKEN="$(vault write -field=token \
|
||||||
|
auth/kubernetes/login role="${vault_role}" jwt="${jwt}" 2>&1)"
|
||||||
|
login_status=$?
|
||||||
|
set -e
|
||||||
|
unset jwt
|
||||||
|
if [ "${login_status}" -eq 0 ] && [ -n "${VAULT_TOKEN}" ]; then
|
||||||
|
export VAULT_TOKEN
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
log "Vault Kubernetes login not ready (attempt ${attempt}/40); retrying"
|
||||||
|
sleep 30
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
done
|
||||||
|
log "Vault Kubernetes login failed after waiting for the auth role"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Return 0 for the complete field, 10 when absent, 11 when present but
|
||||||
|
# incomplete, and 12 for a persistent authorization/transport failure.
|
||||||
|
read_lan_token() {
|
||||||
|
attempt=1
|
||||||
|
while [ "${attempt}" -le 5 ]; do
|
||||||
|
set +e
|
||||||
|
secret_json="$(vault read -format=json "${secret_api_path}" 2>&1)"
|
||||||
|
secret_status=$?
|
||||||
|
set -e
|
||||||
|
if [ "${secret_status}" -eq 0 ]; then
|
||||||
|
unset secret_json
|
||||||
|
set +e
|
||||||
|
lan_token="$(vault kv get -field=token "${secret_path}" 2>&1)"
|
||||||
|
field_status=$?
|
||||||
|
set -e
|
||||||
|
if [ "${field_status}" -eq 0 ]; then
|
||||||
|
case "${lan_token}" in
|
||||||
|
''|*[!0-9a-f]*) unset lan_token; return 11 ;;
|
||||||
|
esac
|
||||||
|
if [ "${#lan_token}" -eq 64 ]; then
|
||||||
|
unset lan_token
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
unset lan_token
|
||||||
|
return 11
|
||||||
|
fi
|
||||||
|
if printf '%s' "${lan_token}" | grep -q 'No value found'; then
|
||||||
|
unset lan_token
|
||||||
|
return 11
|
||||||
|
fi
|
||||||
|
unset lan_token
|
||||||
|
elif printf '%s' "${secret_json}" | grep -Eq 'Code: 404|No value found at'; then
|
||||||
|
unset secret_json
|
||||||
|
return 10
|
||||||
|
fi
|
||||||
|
if [ "${attempt}" -lt 5 ]; then
|
||||||
|
sleep "${attempt}"
|
||||||
|
fi
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
done
|
||||||
|
unset secret_json
|
||||||
|
return 12
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_token
|
||||||
|
|
||||||
|
if read_lan_token; then
|
||||||
|
read_status=0
|
||||||
|
else
|
||||||
|
read_status=$?
|
||||||
|
fi
|
||||||
|
case "${read_status}" in
|
||||||
|
0)
|
||||||
|
log "LAN API token already present; no write performed"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
11)
|
||||||
|
log "secret exists without a valid token; refusing to overwrite existing fields"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
12)
|
||||||
|
log "secret read failed after retries; refusing to seed"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
10) ;;
|
||||||
|
*)
|
||||||
|
log "unexpected secret read state ${read_status}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
new_token="$(retry_command vault write -field=random_bytes \
|
||||||
|
sys/tools/random/32 format=hex)" || {
|
||||||
|
log "Vault random token generation failed after retries"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
case "${new_token}" in
|
||||||
|
''|*[!0-9a-f]*)
|
||||||
|
unset new_token
|
||||||
|
log "Vault returned an invalid random token"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if [ "${#new_token}" -ne 64 ]; then
|
||||||
|
unset new_token
|
||||||
|
log "Vault returned an invalid random token"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
umask 077
|
||||||
|
printf '{"options":{"cas":0},"data":{"token":"%s"}}\n' \
|
||||||
|
"${new_token}" > "${payload_file}"
|
||||||
|
unset new_token
|
||||||
|
|
||||||
|
set +e
|
||||||
|
create_error="$(vault write "${secret_api_path}" @"${payload_file}" 2>&1 >/dev/null)"
|
||||||
|
create_status=$?
|
||||||
|
set -e
|
||||||
|
cleanup
|
||||||
|
if [ "${create_status}" -eq 0 ]; then
|
||||||
|
log "LAN API token created with KV-v2 CAS create-only semantics"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if printf '%s' "${create_error}" | grep -qi 'check-and-set'; then
|
||||||
|
unset create_error
|
||||||
|
if read_lan_token; then
|
||||||
|
log "another seeder won CAS; existing token preserved"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
log "CAS race did not produce a readable token; refusing to continue"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset create_error
|
||||||
|
log "KV-v2 CAS create failed; refusing to retry a write"
|
||||||
|
exit 1
|
||||||
@ -278,6 +278,23 @@ write_policy_and_role "hermes-agent" "hermes" "hermes-agent" \
|
|||||||
"hermes/agent-oidc hermes/agent-tokens hermes/chat-telegram hermes/developer-keycloak hermes/developer-harbor hermes/developer-jenkins hermes/developer-ssh" ""
|
"hermes/agent-oidc hermes/agent-tokens hermes/chat-telegram hermes/developer-keycloak hermes/developer-harbor hermes/developer-jenkins hermes/developer-ssh" ""
|
||||||
write_policy_and_role "hermes-switchyard" "hermes" "hermes-switchyard" \
|
write_policy_and_role "hermes-switchyard" "hermes" "hermes-switchyard" \
|
||||||
"hermes/chat-telegram" ""
|
"hermes/chat-telegram" ""
|
||||||
|
write_policy_and_role "hermes-model-gate" "hermes" "hermes-model-gate" \
|
||||||
|
"hermes/model-gate-lan-api" ""
|
||||||
|
hermes_model_gate_lan_token_seed_policy='
|
||||||
|
path "kv/data/atlas/hermes/model-gate-lan-api" {
|
||||||
|
capabilities = ["create", "read"]
|
||||||
|
}
|
||||||
|
path "sys/tools/random/32" {
|
||||||
|
capabilities = ["update"]
|
||||||
|
}
|
||||||
|
'
|
||||||
|
write_raw_policy "hermes-model-gate-lan-token-seed" "${hermes_model_gate_lan_token_seed_policy}"
|
||||||
|
log "writing role hermes-model-gate-lan-token-seed"
|
||||||
|
vault_cmd write "auth/kubernetes/role/hermes-model-gate-lan-token-seed" \
|
||||||
|
bound_service_account_names="hermes-model-gate-lan-token-seed" \
|
||||||
|
bound_service_account_namespaces="vault" \
|
||||||
|
policies="hermes-model-gate-lan-token-seed" \
|
||||||
|
ttl="${role_ttl}"
|
||||||
hermes_jenkins_token_seed_policy='
|
hermes_jenkins_token_seed_policy='
|
||||||
path "kv/data/atlas/hermes/developer-jenkins" {
|
path "kv/data/atlas/hermes/developer-jenkins" {
|
||||||
capabilities = ["create", "read"]
|
capabilities = ["create", "read"]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user