atlas-iac/services/keycloak/bootstrap-jobs/metis-node-passwords-secret-ensure-job.yaml

114 lines
5.2 KiB
YAML

# services/keycloak/bootstrap-jobs/metis-node-passwords-secret-ensure-job.yaml
# One-off job for sso/metis-node-passwords-secret-ensure-5.
# Purpose: ensure per-node Metis recovery placeholders exist in Vault.
# Atlas/root values are preserved while intranet IPs are standardized per node.
apiVersion: batch/v1
kind: Job
metadata:
name: metis-node-passwords-secret-ensure-5
namespace: sso
spec:
backoffLimit: 0
template:
spec:
serviceAccountName: mas-secrets-ensure
restartPolicy: Never
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.kubernetes.io/worker
operator: Exists
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: kubernetes.io/arch
operator: In
values: ["arm64"]
containers:
- name: apply
image: registry.bstein.dev/bstein/kubectl:1.35.0
command: ["/bin/sh", "-c"]
args:
- |
set -eu
umask 077
work_dir="$(mktemp -d)"
trap 'rm -rf "${work_dir}"' EXIT HUP INT TERM
vault_addr="${VAULT_ADDR:-http://vault.vault.svc.cluster.local:8200}"
vault_role="${VAULT_ROLE:-sso-secrets}"
jwt_file="${SERVICE_ACCOUNT_TOKEN_FILE:-/var/run/secrets/kubernetes.io/serviceaccount/token}"
jq -nc --rawfile jwt "${jwt_file}" --arg role "${vault_role}" \
'{jwt:($jwt|rtrimstr("\n")),role:$role}' > "${work_dir}/login.json"
curl -fsS --max-time 30 --request POST \
--data-binary "@${work_dir}/login.json" \
"${vault_addr}/v1/auth/kubernetes/login" > "${work_dir}/auth.json"
jq -er '.auth.client_token | select(type == "string" and length > 0) |
"header = " + (("X-Vault-Token: " + .) | @json)' \
"${work_dir}/auth.json" > "${work_dir}/auth.curl"
ensured=0
while read -r node intranet_ip; do
[ -n "${node}" ] && [ -n "${intranet_ip}" ] || continue
secret_path="kv/data/atlas/nodes/${node}"
read_status="$(curl -sS --max-time 30 --config "${work_dir}/auth.curl" \
-o "${work_dir}/read.json" -w '%{http_code}' \
"${vault_addr}/v1/${secret_path}")"
case "${read_status}" in
200) ;;
404) printf '%s\n' '{"data":{"data":{},"metadata":{"version":0}}}' > "${work_dir}/read.json" ;;
*) echo "Vault read failed for ${node} (HTTP ${read_status})" >&2; exit 1 ;;
esac
# Preserve all existing fields. CAS prevents a concurrent password update
# from being overwritten; unchanged records do not create new versions.
jq -e --arg ip "${intranet_ip}" '
.data as $existing |
{options:{cas:$existing.metadata.version},
data:({atlas_password:"",root_password:""} + $existing.data + {intranet_ip:$ip})}
' "${work_dir}/read.json" > "${work_dir}/write.json"
if jq -e --slurpfile update "${work_dir}/write.json" \
'.data.data == $update[0].data' "${work_dir}/read.json" >/dev/null; then
continue
fi
write_status="$(curl -sS --max-time 30 --config "${work_dir}/auth.curl" \
-o "${work_dir}/result.json" -w '%{http_code}' --request POST \
-H 'Content-Type: application/json' --data-binary "@${work_dir}/write.json" \
"${vault_addr}/v1/${secret_path}")"
case "${write_status}" in
200|204) ;;
*) echo "Vault update failed for ${node} (HTTP ${write_status}); no retry over concurrent writes" >&2; exit 1 ;;
esac
ensured=$((ensured + 1))
echo "Updated node metadata: ${node}"
done <<'EOF_NODES'
titan-jh 192.168.22.8
titan-db 192.168.22.10
titan-0a 192.168.22.11
titan-0b 192.168.22.12
titan-0c 192.168.22.13
titan-20 192.168.22.20
titan-21 192.168.22.21
titan-22 192.168.22.22
titan-23 192.168.22.24
titan-24 192.168.22.26
titan-04 192.168.22.30
titan-05 192.168.22.31
titan-06 192.168.22.32
titan-07 192.168.22.33
titan-08 192.168.22.34
titan-09 192.168.22.35
titan-10 192.168.22.36
titan-11 192.168.22.37
titan-12 192.168.22.40
titan-13 192.168.22.41
titan-14 192.168.22.42
titan-15 192.168.22.43
titan-16 192.168.22.44
titan-17 192.168.22.45
titan-18 192.168.22.46
titan-19 192.168.22.47
EOF_NODES
echo "Updated ${ensured} Metis node records; unchanged records preserved"