finance: switch vault seed to python
This commit is contained in:
parent
9047dfa3b5
commit
1a3d35094e
@ -2,7 +2,7 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: finance-secrets-ensure-3
|
||||
name: finance-secrets-ensure-4
|
||||
namespace: finance
|
||||
spec:
|
||||
backoffLimit: 1
|
||||
@ -31,13 +31,12 @@ spec:
|
||||
node-role.kubernetes.io/worker: "true"
|
||||
containers:
|
||||
- name: ensure
|
||||
image: alpine:3.20
|
||||
image: python:3.11-alpine
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
set -e
|
||||
apk add --no-cache bash curl jq >/dev/null
|
||||
exec bash /scripts/finance_secrets_ensure.sh
|
||||
exec python /scripts/finance_secrets_ensure.py
|
||||
env:
|
||||
- name: VAULT_ROLE
|
||||
value: finance-secrets
|
||||
|
||||
@ -28,4 +28,4 @@ configMapGenerator:
|
||||
- firefly_user_sync.php=scripts/firefly_user_sync.php
|
||||
- name: finance-secrets-ensure-script
|
||||
files:
|
||||
- finance_secrets_ensure.sh=scripts/finance_secrets_ensure.sh
|
||||
- finance_secrets_ensure.py=scripts/finance_secrets_ensure.py
|
||||
|
||||
175
services/finance/scripts/finance_secrets_ensure.py
Normal file
175
services/finance/scripts/finance_secrets_ensure.py
Normal file
@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env python3
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import secrets
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def read_file(path: Path) -> str:
|
||||
if not path.exists():
|
||||
return ""
|
||||
return path.read_text(encoding="utf-8").strip()
|
||||
|
||||
|
||||
def require_value(label: str, value: str) -> None:
|
||||
if not value:
|
||||
raise RuntimeError(f"missing {label}")
|
||||
|
||||
|
||||
def http_json(method: str, url: str, headers=None, payload=None):
|
||||
data = None
|
||||
if payload is not None:
|
||||
data = json.dumps(payload).encode()
|
||||
req = urllib.request.Request(url, data=data, headers=headers or {}, method=method)
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
body = resp.read()
|
||||
if not body:
|
||||
return resp.status, None
|
||||
return resp.status, json.loads(body.decode())
|
||||
|
||||
|
||||
def vault_login(vault_addr: str, role: str, jwt: str) -> str:
|
||||
status, body = http_json(
|
||||
"POST",
|
||||
f"{vault_addr}/v1/auth/kubernetes/login",
|
||||
headers={"Content-Type": "application/json"},
|
||||
payload={"jwt": jwt, "role": role},
|
||||
)
|
||||
if status != 200 or not body:
|
||||
raise RuntimeError("vault login failed")
|
||||
token = body.get("auth", {}).get("client_token")
|
||||
if not token:
|
||||
raise RuntimeError("vault login returned no token")
|
||||
return token
|
||||
|
||||
|
||||
def vault_read(vault_addr: str, token: str, path: str):
|
||||
try:
|
||||
status, body = http_json(
|
||||
"GET",
|
||||
f"{vault_addr}/v1/kv/data/atlas/{path}",
|
||||
headers={"X-Vault-Token": token},
|
||||
)
|
||||
except urllib.error.HTTPError as exc:
|
||||
if exc.code == 404:
|
||||
return {}
|
||||
raise
|
||||
if status != 200 or not body:
|
||||
return {}
|
||||
return body.get("data", {}).get("data", {}) or {}
|
||||
|
||||
|
||||
def vault_write(vault_addr: str, token: str, path: str, data: dict):
|
||||
payload = {"data": data}
|
||||
status, _ = http_json(
|
||||
"POST",
|
||||
f"{vault_addr}/v1/kv/data/atlas/{path}",
|
||||
headers={"X-Vault-Token": token, "Content-Type": "application/json"},
|
||||
payload=payload,
|
||||
)
|
||||
if status not in (200, 204):
|
||||
raise RuntimeError(f"vault write failed for {path} (status {status})")
|
||||
|
||||
|
||||
def ensure_firefly_db(vault_addr: str, token: str):
|
||||
base = Path("/secrets/firefly-db")
|
||||
host = read_file(base / "DB_HOST") or read_file(base / "DB_HOSTNAME")
|
||||
port = read_file(base / "DB_PORT")
|
||||
db_name = read_file(base / "DB_DATABASE") or read_file(base / "DB_NAME")
|
||||
user = read_file(base / "DB_USERNAME") or read_file(base / "DB_USER")
|
||||
password = read_file(base / "DB_PASSWORD") or read_file(base / "DB_PASS")
|
||||
|
||||
require_value("firefly-db/DB_HOST", host)
|
||||
require_value("firefly-db/DB_PORT", port)
|
||||
require_value("firefly-db/DB_DATABASE", db_name)
|
||||
require_value("firefly-db/DB_USERNAME", user)
|
||||
require_value("firefly-db/DB_PASSWORD", password)
|
||||
|
||||
vault_write(
|
||||
vault_addr,
|
||||
token,
|
||||
"finance/firefly-db",
|
||||
{
|
||||
"DB_HOST": host,
|
||||
"DB_PORT": port,
|
||||
"DB_DATABASE": db_name,
|
||||
"DB_USERNAME": user,
|
||||
"DB_PASSWORD": password,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def ensure_firefly_secrets(vault_addr: str, token: str):
|
||||
current = vault_read(vault_addr, token, "finance/firefly-secrets")
|
||||
app_key = current.get("APP_KEY")
|
||||
if not app_key:
|
||||
app_key = "base64:" + base64.b64encode(secrets.token_bytes(32)).decode()
|
||||
cron_token = current.get("STATIC_CRON_TOKEN")
|
||||
if not cron_token:
|
||||
cron_token = secrets.token_urlsafe(32)
|
||||
vault_write(
|
||||
vault_addr,
|
||||
token,
|
||||
"finance/firefly-secrets",
|
||||
{"APP_KEY": app_key, "STATIC_CRON_TOKEN": cron_token},
|
||||
)
|
||||
|
||||
|
||||
def ensure_actual_db(vault_addr: str, token: str):
|
||||
base = Path("/secrets/actualbudget-db")
|
||||
if not base.exists():
|
||||
return
|
||||
host = read_file(base / "DB_HOST") or read_file(base / "DB_HOSTNAME")
|
||||
port = read_file(base / "DB_PORT")
|
||||
db_name = read_file(base / "DB_DATABASE") or read_file(base / "DB_NAME")
|
||||
user = read_file(base / "DB_USERNAME") or read_file(base / "DB_USER")
|
||||
password = read_file(base / "DB_PASSWORD") or read_file(base / "DB_PASS")
|
||||
|
||||
if not any([host, port, db_name, user, password]):
|
||||
return
|
||||
|
||||
require_value("actualbudget-db/DB_HOST", host)
|
||||
require_value("actualbudget-db/DB_PORT", port)
|
||||
require_value("actualbudget-db/DB_DATABASE", db_name)
|
||||
require_value("actualbudget-db/DB_USERNAME", user)
|
||||
require_value("actualbudget-db/DB_PASSWORD", password)
|
||||
|
||||
vault_write(
|
||||
vault_addr,
|
||||
token,
|
||||
"finance/actual-db",
|
||||
{
|
||||
"DB_HOST": host,
|
||||
"DB_PORT": port,
|
||||
"DB_DATABASE": db_name,
|
||||
"DB_USERNAME": user,
|
||||
"DB_PASSWORD": password,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
vault_addr = os.environ.get("VAULT_ADDR", "http://vault.vault.svc.cluster.local:8200")
|
||||
vault_role = os.environ.get("VAULT_ROLE", "finance-secrets")
|
||||
jwt = read_file(Path("/var/run/secrets/kubernetes.io/serviceaccount/token"))
|
||||
if not jwt:
|
||||
raise RuntimeError("missing service account token")
|
||||
|
||||
token = vault_login(vault_addr, vault_role, jwt)
|
||||
ensure_firefly_db(vault_addr, token)
|
||||
ensure_firefly_secrets(vault_addr, token)
|
||||
ensure_actual_db(vault_addr, token)
|
||||
print("finance secrets ensured")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
sys.exit(main())
|
||||
except Exception as exc:
|
||||
print(f"finance secrets ensure failed: {exc}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@ -1,130 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
vault_addr="${VAULT_ADDR:-http://vault.vault.svc.cluster.local:8200}"
|
||||
vault_role="${VAULT_ROLE:-finance-secrets}"
|
||||
jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
|
||||
login_payload="$(jq -nc --arg jwt "${jwt}" --arg role "${vault_role}" '{jwt:$jwt, role:$role}')"
|
||||
vault_token="$(curl -sS --request POST --data "${login_payload}" \
|
||||
"${vault_addr}/v1/auth/kubernetes/login" | jq -r '.auth.client_token')"
|
||||
if [ -z "${vault_token}" ] || [ "${vault_token}" = "null" ]; then
|
||||
echo "vault login failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
read_secret() {
|
||||
path="$1"
|
||||
if [ -f "${path}" ]; then
|
||||
cat "${path}"
|
||||
fi
|
||||
}
|
||||
|
||||
require_value() {
|
||||
label="$1"
|
||||
value="$2"
|
||||
if [ -z "${value}" ]; then
|
||||
echo "missing ${label}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
vault_read() {
|
||||
path="$1"
|
||||
key="$2"
|
||||
curl -sS -H "X-Vault-Token: ${vault_token}" \
|
||||
"${vault_addr}/v1/kv/data/atlas/${path}" 2>/dev/null | \
|
||||
jq -r --arg key "${key}" '.data.data[$key] // empty' 2>/dev/null || true
|
||||
}
|
||||
|
||||
vault_write_json() {
|
||||
path="$1"
|
||||
payload="$2"
|
||||
curl -sS -X POST -H "X-Vault-Token: ${vault_token}" \
|
||||
-d "${payload}" "${vault_addr}/v1/kv/data/atlas/${path}" >/dev/null
|
||||
}
|
||||
|
||||
firefly_db_host="$(read_secret /secrets/firefly-db/DB_HOST)"
|
||||
if [ -z "${firefly_db_host}" ]; then
|
||||
firefly_db_host="$(read_secret /secrets/firefly-db/DB_HOSTNAME)"
|
||||
fi
|
||||
firefly_db_port="$(read_secret /secrets/firefly-db/DB_PORT)"
|
||||
firefly_db_name="$(read_secret /secrets/firefly-db/DB_DATABASE)"
|
||||
if [ -z "${firefly_db_name}" ]; then
|
||||
firefly_db_name="$(read_secret /secrets/firefly-db/DB_NAME)"
|
||||
fi
|
||||
firefly_db_user="$(read_secret /secrets/firefly-db/DB_USERNAME)"
|
||||
if [ -z "${firefly_db_user}" ]; then
|
||||
firefly_db_user="$(read_secret /secrets/firefly-db/DB_USER)"
|
||||
fi
|
||||
firefly_db_pass="$(read_secret /secrets/firefly-db/DB_PASSWORD)"
|
||||
if [ -z "${firefly_db_pass}" ]; then
|
||||
firefly_db_pass="$(read_secret /secrets/firefly-db/DB_PASS)"
|
||||
fi
|
||||
|
||||
require_value "firefly-db/DB_HOST" "${firefly_db_host}"
|
||||
require_value "firefly-db/DB_PORT" "${firefly_db_port}"
|
||||
require_value "firefly-db/DB_DATABASE" "${firefly_db_name}"
|
||||
require_value "firefly-db/DB_USERNAME" "${firefly_db_user}"
|
||||
require_value "firefly-db/DB_PASSWORD" "${firefly_db_pass}"
|
||||
|
||||
firefly_payload="$(jq -nc \
|
||||
--arg host "${firefly_db_host}" \
|
||||
--arg port "${firefly_db_port}" \
|
||||
--arg db "${firefly_db_name}" \
|
||||
--arg user "${firefly_db_user}" \
|
||||
--arg pass "${firefly_db_pass}" \
|
||||
'{data:{DB_HOST:$host, DB_PORT:$port, DB_DATABASE:$db, DB_USERNAME:$user, DB_PASSWORD:$pass}}')"
|
||||
vault_write_json "finance/firefly-db" "${firefly_payload}"
|
||||
|
||||
app_key="$(vault_read "finance/firefly-secrets" "APP_KEY")"
|
||||
if [ -z "${app_key}" ]; then
|
||||
app_key="base64:$(head -c 32 /dev/urandom | base64 | tr -d '\n')"
|
||||
fi
|
||||
cron_token="$(vault_read "finance/firefly-secrets" "STATIC_CRON_TOKEN")"
|
||||
if [ -z "${cron_token}" ]; then
|
||||
cron_token="$(head -c 32 /dev/urandom | base64 | tr -d '\n' | tr '+/' '-_' | tr -d '=')"
|
||||
fi
|
||||
firefly_secret_payload="$(jq -nc \
|
||||
--arg app_key "${app_key}" \
|
||||
--arg cron "${cron_token}" \
|
||||
'{data:{APP_KEY:$app_key, STATIC_CRON_TOKEN:$cron}}')"
|
||||
vault_write_json "finance/firefly-secrets" "${firefly_secret_payload}"
|
||||
|
||||
if [ -d /secrets/actualbudget-db ]; then
|
||||
actual_db_host="$(read_secret /secrets/actualbudget-db/DB_HOST)"
|
||||
if [ -z "${actual_db_host}" ]; then
|
||||
actual_db_host="$(read_secret /secrets/actualbudget-db/DB_HOSTNAME)"
|
||||
fi
|
||||
actual_db_port="$(read_secret /secrets/actualbudget-db/DB_PORT)"
|
||||
actual_db_name="$(read_secret /secrets/actualbudget-db/DB_DATABASE)"
|
||||
if [ -z "${actual_db_name}" ]; then
|
||||
actual_db_name="$(read_secret /secrets/actualbudget-db/DB_NAME)"
|
||||
fi
|
||||
actual_db_user="$(read_secret /secrets/actualbudget-db/DB_USERNAME)"
|
||||
if [ -z "${actual_db_user}" ]; then
|
||||
actual_db_user="$(read_secret /secrets/actualbudget-db/DB_USER)"
|
||||
fi
|
||||
actual_db_pass="$(read_secret /secrets/actualbudget-db/DB_PASSWORD)"
|
||||
if [ -z "${actual_db_pass}" ]; then
|
||||
actual_db_pass="$(read_secret /secrets/actualbudget-db/DB_PASS)"
|
||||
fi
|
||||
|
||||
if [ -n "${actual_db_host}${actual_db_port}${actual_db_name}${actual_db_user}${actual_db_pass}" ]; then
|
||||
require_value "actualbudget-db/DB_HOST" "${actual_db_host}"
|
||||
require_value "actualbudget-db/DB_PORT" "${actual_db_port}"
|
||||
require_value "actualbudget-db/DB_DATABASE" "${actual_db_name}"
|
||||
require_value "actualbudget-db/DB_USERNAME" "${actual_db_user}"
|
||||
require_value "actualbudget-db/DB_PASSWORD" "${actual_db_pass}"
|
||||
|
||||
actual_payload="$(jq -nc \
|
||||
--arg host "${actual_db_host}" \
|
||||
--arg port "${actual_db_port}" \
|
||||
--arg db "${actual_db_name}" \
|
||||
--arg user "${actual_db_user}" \
|
||||
--arg pass "${actual_db_pass}" \
|
||||
'{data:{DB_HOST:$host, DB_PORT:$port, DB_DATABASE:$db, DB_USERNAME:$user, DB_PASSWORD:$pass}}')"
|
||||
vault_write_json "finance/actual-db" "${actual_payload}"
|
||||
else
|
||||
echo "actualbudget-db secret empty; skipping actual-db vault write" >&2
|
||||
fi
|
||||
fi
|
||||
Loading…
x
Reference in New Issue
Block a user