diff --git a/services/finance/actual-budget-deployment.yaml b/services/finance/actual-budget-deployment.yaml index 11b7e5c..8e76d3e 100644 --- a/services/finance/actual-budget-deployment.yaml +++ b/services/finance/actual-budget-deployment.yaml @@ -22,6 +22,7 @@ spec: app: actual-budget annotations: vault.hashicorp.com/agent-inject: "true" + vault.hashicorp.com/agent-pre-populate-only: "true" vault.hashicorp.com/role: "finance" vault.hashicorp.com/agent-inject-secret-actual-env.sh: "kv/data/atlas/finance/actual-oidc" vault.hashicorp.com/agent-inject-template-actual-env.sh: | diff --git a/services/finance/finance-secrets-ensure-job.yaml b/services/finance/finance-secrets-ensure-job.yaml new file mode 100644 index 0000000..1402d14 --- /dev/null +++ b/services/finance/finance-secrets-ensure-job.yaml @@ -0,0 +1,59 @@ +# services/finance/finance-secrets-ensure-job.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: finance-secrets-ensure-1 + namespace: finance +spec: + backoffLimit: 1 + ttlSecondsAfterFinished: 3600 + template: + spec: + serviceAccountName: finance-secrets-ensure + restartPolicy: Never + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: hardware + operator: In + values: ["rpi5"] + - weight: 70 + preference: + matchExpressions: + - key: hardware + operator: In + values: ["rpi4"] + nodeSelector: + kubernetes.io/arch: arm64 + node-role.kubernetes.io/worker: "true" + containers: + - name: ensure + image: alpine:3.20 + command: ["/scripts/finance_secrets_ensure.sh"] + env: + - name: VAULT_ROLE + value: finance-secrets + volumeMounts: + - name: finance-secrets-ensure-script + mountPath: /scripts + readOnly: true + - name: firefly-db + mountPath: /secrets/firefly-db + readOnly: true + - name: actualbudget-db + mountPath: /secrets/actualbudget-db + readOnly: true + volumes: + - name: finance-secrets-ensure-script + configMap: + name: finance-secrets-ensure-script + defaultMode: 0555 + - name: firefly-db + secret: + secretName: firefly-db + - name: actualbudget-db + secret: + secretName: actualbudget-db diff --git a/services/finance/firefly-deployment.yaml b/services/finance/firefly-deployment.yaml index 1b51a07..ff95dad 100644 --- a/services/finance/firefly-deployment.yaml +++ b/services/finance/firefly-deployment.yaml @@ -123,8 +123,10 @@ spec: value: "587" - name: MAIL_ENCRYPTION value: tls - - name: MAIL_FROM + - name: MAIL_FROM_ADDRESS value: no-reply-firefly@bstein.dev + - name: MAIL_FROM_NAME + value: Firefly III - name: CACHE_DRIVER value: file - name: SESSION_DRIVER diff --git a/services/finance/kustomization.yaml b/services/finance/kustomization.yaml index 8cde8ba..2189834 100644 --- a/services/finance/kustomization.yaml +++ b/services/finance/kustomization.yaml @@ -8,6 +8,7 @@ resources: - portal-rbac.yaml - actual-budget-data-pvc.yaml - firefly-storage-pvc.yaml + - finance-secrets-ensure-job.yaml - actual-budget-deployment.yaml - firefly-deployment.yaml - firefly-user-sync-cronjob.yaml @@ -25,3 +26,6 @@ configMapGenerator: - name: firefly-user-sync-script files: - firefly_user_sync.php=scripts/firefly_user_sync.php + - name: finance-secrets-ensure-script + files: + - finance_secrets_ensure.sh=scripts/finance_secrets_ensure.sh diff --git a/services/finance/scripts/finance_secrets_ensure.sh b/services/finance/scripts/finance_secrets_ensure.sh new file mode 100755 index 0000000..a0dca4a --- /dev/null +++ b/services/finance/scripts/finance_secrets_ensure.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash +set -euo pipefail + +apk add --no-cache curl jq >/dev/null + +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 diff --git a/services/finance/serviceaccount.yaml b/services/finance/serviceaccount.yaml index d57a3d2..3d18681 100644 --- a/services/finance/serviceaccount.yaml +++ b/services/finance/serviceaccount.yaml @@ -4,3 +4,9 @@ kind: ServiceAccount metadata: name: finance-vault namespace: finance +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: finance-secrets-ensure + namespace: finance diff --git a/services/vault/scripts/vault_k8s_auth_configure.sh b/services/vault/scripts/vault_k8s_auth_configure.sh index 04114fc..140f1d4 100644 --- a/services/vault/scripts/vault_k8s_auth_configure.sh +++ b/services/vault/scripts/vault_k8s_auth_configure.sh @@ -216,6 +216,9 @@ write_policy_and_role "health" "health" "health-vault-sync" \ "health/*" "" 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/* harbor-pull/longhorn" "" write_policy_and_role "postgres" "postgres" "postgres-vault" \