77 lines
2.4 KiB
Bash
77 lines
2.4 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
# Sync Keycloak users into Zot htpasswd.
|
||
|
|
# Intended to be rendered into a ConfigMap/Job; keep real secrets out of git.
|
||
|
|
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
require() { command -v "$1" >/dev/null 2>&1 || { echo "missing required binary: $1" >&2; exit 1; }; }
|
||
|
|
require curl; require jq; require kubectl; require htpasswd
|
||
|
|
|
||
|
|
: "${KEYCLOAK_URL:=https://sso.bstein.dev}"
|
||
|
|
: "${KEYCLOAK_REALM:=atlas}"
|
||
|
|
: "${KEYCLOAK_CLIENT_ID:?set KEYCLOAK_CLIENT_ID or export via secret}"
|
||
|
|
: "${KEYCLOAK_CLIENT_SECRET:?set KEYCLOAK_CLIENT_SECRET or export via secret}"
|
||
|
|
: "${ZOT_NAMESPACE:=zot}"
|
||
|
|
: "${HTPASSWD_SECRET_NAME:=zot-htpasswd}"
|
||
|
|
: "${DEFAULT_PASSWORD:=TempSsoPass!2025}"
|
||
|
|
: "${UI_PROXY_USER:=zot-ui-proxy}"
|
||
|
|
: "${UI_PROXY_PASSWORD:=TempSsoUiPass!2025}"
|
||
|
|
|
||
|
|
fetch_token() {
|
||
|
|
curl -fsS -X POST \
|
||
|
|
-d "grant_type=client_credentials" \
|
||
|
|
-d "client_id=${KEYCLOAK_CLIENT_ID}" \
|
||
|
|
-d "client_secret=${KEYCLOAK_CLIENT_SECRET}" \
|
||
|
|
"${KEYCLOAK_URL}/realms/${KEYCLOAK_REALM}/protocol/openid-connect/token" \
|
||
|
|
| jq -r '.access_token'
|
||
|
|
}
|
||
|
|
|
||
|
|
pull_users() {
|
||
|
|
local token="$1"
|
||
|
|
curl -fsS -H "Authorization: Bearer ${token}" \
|
||
|
|
"${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users?max=500" \
|
||
|
|
| jq -r '.[] | select(.enabled == true) | select(.username | startswith("service-account-") | not) | .username'
|
||
|
|
}
|
||
|
|
|
||
|
|
decode_secret_file() {
|
||
|
|
local ns="$1" name="$2" key="$3" out="$4"
|
||
|
|
if kubectl -n "${ns}" get secret "${name}" >/dev/null 2>&1; then
|
||
|
|
kubectl -n "${ns}" get secret "${name}" -o "jsonpath={.data.${key}}" | base64 -d > "${out}" || true
|
||
|
|
else
|
||
|
|
: > "${out}"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
ensure_htpasswd_line() {
|
||
|
|
local user="$1" password="$2" file="$3"
|
||
|
|
if ! grep -q "^${user}:" "${file}" 2>/dev/null; then
|
||
|
|
htpasswd -nbB "${user}" "${password}" >> "${file}"
|
||
|
|
echo "added user ${user} to htpasswd"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
main() {
|
||
|
|
local token tmp existing
|
||
|
|
tmp="$(mktemp)"
|
||
|
|
existing="$(mktemp)"
|
||
|
|
trap 'rm -f "${tmp}" "${existing}"' EXIT
|
||
|
|
|
||
|
|
decode_secret_file "${ZOT_NAMESPACE}" "${HTPASSWD_SECRET_NAME}" htpasswd "${existing}"
|
||
|
|
cp "${existing}" "${tmp}"
|
||
|
|
|
||
|
|
ensure_htpasswd_line "${UI_PROXY_USER}" "${UI_PROXY_PASSWORD}" "${tmp}"
|
||
|
|
|
||
|
|
token="$(fetch_token)"
|
||
|
|
readarray -t users < <(pull_users "${token}")
|
||
|
|
for user in "${users[@]}"; do
|
||
|
|
ensure_htpasswd_line "${user}" "${DEFAULT_PASSWORD}" "${tmp}"
|
||
|
|
done
|
||
|
|
|
||
|
|
kubectl create secret generic "${HTPASSWD_SECRET_NAME}" \
|
||
|
|
--namespace "${ZOT_NAMESPACE}" \
|
||
|
|
--from-file=htpasswd="${tmp}" \
|
||
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
||
|
|
}
|
||
|
|
|
||
|
|
main "$@"
|