zot: add oauth proxy and user sync scripts
This commit is contained in:
parent
828f66d18c
commit
b87f06f6ff
@ -372,8 +372,8 @@ function xmrwallet_bootstrap --description "Interactive setup of monero-wallet-r
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Use your private image by default (in Zot)
|
# Use your private image by default (in Zot)
|
||||||
read -P "Container image for wallet RPC [registry.bstein.dev/infra/monero-wallet-rpc:0.18.4.1]: " image
|
read -P "Container image for wallet RPC [cli.registry.bstein.dev/infra/monero-wallet-rpc:0.18.4.1]: " image
|
||||||
if test -z "$image"; set image registry.bstein.dev/infra/monero-wallet-rpc:0.18.4.1; end
|
if test -z "$image"; set image cli.registry.bstein.dev/infra/monero-wallet-rpc:0.18.4.1; end
|
||||||
_require "Container image" $image; or return 1
|
_require "Container image" $image; or return 1
|
||||||
|
|
||||||
# --- Secrets (defaults: RPC user=wallet name, passwords auto if missing)
|
# --- Secrets (defaults: RPC user=wallet name, passwords auto if missing)
|
||||||
@ -1375,4 +1375,3 @@ function xmrwallet_help_detailed
|
|||||||
echo " Probes it via a temporary port-forward so it works from your workstation."
|
echo " Probes it via a temporary port-forward so it works from your workstation."
|
||||||
echo " Set xmrwallet_SKIP_DAEMON_CHECK=1 to bypass the daemon probe (not recommended)."
|
echo " Set xmrwallet_SKIP_DAEMON_CHECK=1 to bypass the daemon probe (not recommended)."
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ end
|
|||||||
|
|
||||||
# Default image chooser (you should override with your own multi-arch image)
|
# Default image chooser (you should override with your own multi-arch image)
|
||||||
function _sui_default_image -a NET
|
function _sui_default_image -a NET
|
||||||
echo registry.bstein.dev/infra/sui-tools:1.53.2
|
echo cli.registry.bstein.dev/infra/sui-tools:1.53.2
|
||||||
end
|
end
|
||||||
|
|
||||||
# Convert any string to a k8s-safe name (RFC-1123 label-ish)
|
# Convert any string to a k8s-safe name (RFC-1123 label-ish)
|
||||||
|
|||||||
92
scripts/gitea_cred_sync.sh
Executable file
92
scripts/gitea_cred_sync.sh
Executable file
@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Sync Keycloak users into Gitea local accounts (for CLI + tokens).
|
||||||
|
# Requires: curl, jq, kubectl. Expects a Keycloak client with realm-management
|
||||||
|
# permissions (manage-users) and a Gitea admin token stored in a secret.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
: "${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}"
|
||||||
|
: "${GITEA_BASE_URL:=https://scm.bstein.dev}"
|
||||||
|
: "${GITEA_NAMESPACE:=gitea}"
|
||||||
|
: "${GITEA_TOKEN_SECRET_NAME:=gitea-admin-token}"
|
||||||
|
: "${GITEA_TOKEN_SECRET_KEY:=token}"
|
||||||
|
: "${DEFAULT_PASSWORD:=TempSsoPass!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, (.email // ""), (.firstName // ""), (.lastName // "")] | @tsv'
|
||||||
|
}
|
||||||
|
|
||||||
|
get_gitea_token() {
|
||||||
|
if [[ -n "${GITEA_ADMIN_TOKEN:-}" ]]; then
|
||||||
|
echo "${GITEA_ADMIN_TOKEN}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
kubectl -n "${GITEA_NAMESPACE}" get secret "${GITEA_TOKEN_SECRET_NAME}" -o "jsonpath={.data.${GITEA_TOKEN_SECRET_KEY}}" \
|
||||||
|
| base64 -d
|
||||||
|
}
|
||||||
|
|
||||||
|
user_exists() {
|
||||||
|
local token="$1" username="$2"
|
||||||
|
local code
|
||||||
|
code=$(curl -s -o /dev/null -w '%{http_code}' \
|
||||||
|
-H "Authorization: token ${token}" \
|
||||||
|
"${GITEA_BASE_URL}/api/v1/admin/users/${username}")
|
||||||
|
[[ "${code}" == "200" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
create_user() {
|
||||||
|
local token="$1" username="$2" email="$3" fname="$4" lname="$5"
|
||||||
|
local body status fullname
|
||||||
|
fullname="$(echo "${fname} ${lname}" | xargs)"
|
||||||
|
if [[ -z "${email}" ]]; then
|
||||||
|
email="${username}@example.local"
|
||||||
|
fi
|
||||||
|
body=$(jq -n --arg u "${username}" --arg e "${email}" --arg p "${DEFAULT_PASSWORD}" \
|
||||||
|
--arg fn "${fullname}" '{username:$u, email:$e, password:$p, must_change_password:false, full_name:$fn}')
|
||||||
|
status=$(curl -s -o /dev/null -w '%{http_code}' \
|
||||||
|
-H "Authorization: token ${token}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X POST \
|
||||||
|
-d "${body}" \
|
||||||
|
"${GITEA_BASE_URL}/api/v1/admin/users")
|
||||||
|
if [[ "${status}" == "201" ]]; then
|
||||||
|
echo "created gitea user ${username}"
|
||||||
|
elif [[ "${status}" == "409" ]]; then
|
||||||
|
echo "gitea user ${username} already exists (409)" >&2
|
||||||
|
else
|
||||||
|
echo "failed to create gitea user ${username} (status ${status})" >&2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local kc_token gitea_token
|
||||||
|
kc_token="$(fetch_token)"
|
||||||
|
gitea_token="$(get_gitea_token)"
|
||||||
|
|
||||||
|
while IFS=$'\t' read -r username email fname lname; do
|
||||||
|
if user_exists "${gitea_token}" "${username}"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
create_user "${gitea_token}" "${username}" "${email}" "${fname}" "${lname}"
|
||||||
|
done < <(pull_users "${kc_token}")
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
87
scripts/gitops_cred_sync.sh
Executable file
87
scripts/gitops_cred_sync.sh
Executable file
@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Ensure Keycloak users are in the GitOps admin group used by weave-gitops (cd.bstein.dev).
|
||||||
|
# Weave GitOps relies on OIDC; membership in the "admin" group maps to cluster-admin via RBAC.
|
||||||
|
# Requires: curl, jq. Needs a Keycloak client with realm-management (manage-users/groups).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
require() { command -v "$1" >/dev/null 2>&1 || { echo "missing required binary: $1" >&2; exit 1; }; }
|
||||||
|
require curl; require jq
|
||||||
|
|
||||||
|
: "${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}"
|
||||||
|
: "${GITOPS_GROUP:=admin}"
|
||||||
|
# Comma-separated usernames to sync; set SYNC_ALL_USERS=true to include all Keycloak users.
|
||||||
|
: "${TARGET_USERNAMES:=bstein}"
|
||||||
|
: "${SYNC_ALL_USERS:=false}"
|
||||||
|
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_group() {
|
||||||
|
local token="$1" group="$2" id
|
||||||
|
id=$(curl -fsS -H "Authorization: Bearer ${token}" \
|
||||||
|
"${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/groups?search=${group}" \
|
||||||
|
| jq -r --arg g "${group}" '.[] | select(.name==$g) | .id' | head -n1)
|
||||||
|
if [[ -n "${id}" ]]; then
|
||||||
|
echo "${id}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
curl -fsS -H "Authorization: Bearer ${token}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"${group}\"}" \
|
||||||
|
-X POST "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/groups"
|
||||||
|
# Fetch again to get id
|
||||||
|
curl -fsS -H "Authorization: Bearer ${token}" \
|
||||||
|
"${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/groups?search=${group}" \
|
||||||
|
| jq -r --arg g "${group}" '.[] | select(.name==$g) | .id' | head -n1
|
||||||
|
}
|
||||||
|
|
||||||
|
user_id_by_name() {
|
||||||
|
local token="$1" username="$2"
|
||||||
|
curl -fsS -H "Authorization: Bearer ${token}" \
|
||||||
|
"${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users?username=${username}" \
|
||||||
|
| jq -r '.[0].id'
|
||||||
|
}
|
||||||
|
|
||||||
|
add_user_to_group() {
|
||||||
|
local token="$1" user_id="$2" group_id="$3" username="$4"
|
||||||
|
if [[ -z "${user_id}" ]]; then
|
||||||
|
echo "user ${username} not found in Keycloak; skip" >&2
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
curl -fsS -o /dev/null -w '%{http_code}' \
|
||||||
|
-H "Authorization: Bearer ${token}" \
|
||||||
|
-X PUT "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users/${user_id}/groups/${group_id}" \
|
||||||
|
| grep -qE '^(204|409)$' || echo "failed adding ${username} to group" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local token group_id users=()
|
||||||
|
token="$(fetch_token)"
|
||||||
|
group_id="$(ensure_group "${token}" "${GITOPS_GROUP}")"
|
||||||
|
|
||||||
|
if [[ "${SYNC_ALL_USERS}" == "true" ]]; then
|
||||||
|
readarray -t users < <(curl -fsS -H "Authorization: Bearer ${token}" \
|
||||||
|
"${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users?max=500" \
|
||||||
|
| jq -r '.[] | select(.enabled==true) | .username')
|
||||||
|
else
|
||||||
|
IFS=',' read -ra users <<< "${TARGET_USERNAMES}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for user in "${users[@]}"; do
|
||||||
|
user="$(echo "${user}" | xargs)"
|
||||||
|
[[ -z "${user}" ]] && continue
|
||||||
|
add_user_to_group "${token}" "$(user_id_by_name "${token}" "${user}")" "${group_id}" "${user}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
94
scripts/jenkins_cred_sync.sh
Executable file
94
scripts/jenkins_cred_sync.sh
Executable file
@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Sync Keycloak users into Jenkins local accounts (for CLI/API use).
|
||||||
|
# Jenkins is OIDC-enabled, but local users can still be provisioned for tokens.
|
||||||
|
# Requires: curl, jq, kubectl. Needs Jenkins admin user+API token.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
: "${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}"
|
||||||
|
: "${JENKINS_URL:=https://ci.bstein.dev}"
|
||||||
|
: "${JENKINS_NAMESPACE:=jenkins}"
|
||||||
|
: "${JENKINS_ADMIN_SECRET_NAME:=jenkins-admin-token}"
|
||||||
|
: "${JENKINS_ADMIN_USER_KEY:=username}"
|
||||||
|
: "${JENKINS_ADMIN_TOKEN_KEY:=token}"
|
||||||
|
: "${DEFAULT_PASSWORD:=TempSsoPass!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) | [.id, .username, (.email // "")] | @tsv'
|
||||||
|
}
|
||||||
|
|
||||||
|
get_admin_auth() {
|
||||||
|
local user token
|
||||||
|
if [[ -n "${JENKINS_ADMIN_USER:-}" && -n "${JENKINS_ADMIN_TOKEN:-}" ]]; then
|
||||||
|
echo "${JENKINS_ADMIN_USER}:${JENKINS_ADMIN_TOKEN}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
user=$(kubectl -n "${JENKINS_NAMESPACE}" get secret "${JENKINS_ADMIN_SECRET_NAME}" -o "jsonpath={.data.${JENKINS_ADMIN_USER_KEY}}" | base64 -d)
|
||||||
|
token=$(kubectl -n "${JENKINS_NAMESPACE}" get secret "${JENKINS_ADMIN_SECRET_NAME}" -o "jsonpath={.data.${JENKINS_ADMIN_TOKEN_KEY}}" | base64 -d)
|
||||||
|
echo "${user}:${token}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_crumb() {
|
||||||
|
local auth="$1"
|
||||||
|
curl -fsS -u "${auth}" "${JENKINS_URL}/crumbIssuer/api/json" | jq -r .crumb
|
||||||
|
}
|
||||||
|
|
||||||
|
user_exists() {
|
||||||
|
local auth="$1" user="$2"
|
||||||
|
local code
|
||||||
|
code=$(curl -s -o /dev/null -w '%{http_code}' -u "${auth}" "${JENKINS_URL}/user/${user}/api/json")
|
||||||
|
[[ "${code}" == "200" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
create_user() {
|
||||||
|
local auth="$1" crumb="$2" username="$3" email="$4"
|
||||||
|
local status
|
||||||
|
status=$(curl -s -o /dev/null -w '%{http_code}' \
|
||||||
|
-u "${auth}" \
|
||||||
|
-H "Jenkins-Crumb: ${crumb}" \
|
||||||
|
-X POST \
|
||||||
|
--data "username=${username}&password1=${DEFAULT_PASSWORD}&password2=${DEFAULT_PASSWORD}&fullname=${username}&email=${email}" \
|
||||||
|
"${JENKINS_URL}/securityRealm/createAccountByAdmin")
|
||||||
|
|
||||||
|
if [[ "${status}" == "200" || "${status}" == "302" ]]; then
|
||||||
|
echo "created jenkins user ${username}"
|
||||||
|
elif [[ "${status}" == "400" ]]; then
|
||||||
|
echo "jenkins user ${username} already exists (400)" >&2
|
||||||
|
else
|
||||||
|
echo "failed to create jenkins user ${username} (status ${status})" >&2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local kc_token auth crumb
|
||||||
|
kc_token="$(fetch_token)"
|
||||||
|
auth="$(get_admin_auth)"
|
||||||
|
crumb="$(get_crumb "${auth}")"
|
||||||
|
|
||||||
|
while IFS=$'\t' read -r _ uid email; do
|
||||||
|
if user_exists "${auth}" "${uid}"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
create_user "${auth}" "${crumb}" "${uid}" "${email}"
|
||||||
|
done < <(pull_users "${kc_token}")
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env fish
|
#!/usr/bin/env fish
|
||||||
|
|
||||||
function pvc-usage --description "Show Longhorn PVC usage (human-readable) mapped to namespace/name"
|
function pvc-usage --description "Show Longhorn PVC usage mapped to namespace/name"
|
||||||
begin
|
begin
|
||||||
kubectl -n longhorn-system get volumes.longhorn.io -o json \
|
kubectl -n longhorn-system get volumes.longhorn.io -o json \
|
||||||
| jq -r '.items[] | "\(.metadata.name)\t\(.status.actualSize)\t\(.spec.size)"' \
|
| jq -r '.items[] | "\(.metadata.name)\t\(.status.actualSize)\t\(.spec.size)"' \
|
||||||
|
|||||||
@ -39,7 +39,7 @@ SITES=(
|
|||||||
"Jellyfin|https://stream.bstein.dev"
|
"Jellyfin|https://stream.bstein.dev"
|
||||||
"Gitea|https://scm.bstein.dev"
|
"Gitea|https://scm.bstein.dev"
|
||||||
"Jenkins|https://ci.bstein.dev"
|
"Jenkins|https://ci.bstein.dev"
|
||||||
"Zot|https://registry.bstein.dev"
|
"Zot|https://web.registry.bstein.dev"
|
||||||
"Vault|https://secret.bstein.dev"
|
"Vault|https://secret.bstein.dev"
|
||||||
"Jitsi|https://meet.bstein.dev"
|
"Jitsi|https://meet.bstein.dev"
|
||||||
"Grafana|https://metrics.bstein.dev"
|
"Grafana|https://metrics.bstein.dev"
|
||||||
|
|||||||
76
scripts/zot_cred_sync.sh
Executable file
76
scripts/zot_cred_sync.sh
Executable file
@ -0,0 +1,76 @@
|
|||||||
|
#!/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 "$@"
|
||||||
@ -35,7 +35,7 @@ spec:
|
|||||||
values: ["rpi4"]
|
values: ["rpi4"]
|
||||||
containers:
|
containers:
|
||||||
- name: monerod
|
- name: monerod
|
||||||
image: registry.bstein.dev/infra/monerod:0.18.4.1
|
image: cli.registry.bstein.dev/infra/monerod:0.18.4.1
|
||||||
command: ["/opt/monero/monerod"]
|
command: ["/opt/monero/monerod"]
|
||||||
args:
|
args:
|
||||||
- --data-dir=/data
|
- --data-dir=/data
|
||||||
|
|||||||
@ -32,7 +32,7 @@ spec:
|
|||||||
values: ["rpi4"]
|
values: ["rpi4"]
|
||||||
containers:
|
containers:
|
||||||
- name: monero-p2pool
|
- name: monero-p2pool
|
||||||
image: registry.bstein.dev/infra/monero-p2pool:4.9
|
image: cli.registry.bstein.dev/infra/monero-p2pool:4.9
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
command: ["p2pool"]
|
command: ["p2pool"]
|
||||||
args:
|
args:
|
||||||
|
|||||||
@ -63,7 +63,7 @@ spec:
|
|||||||
--key "$CLIENT_ID" \
|
--key "$CLIENT_ID" \
|
||||||
--secret "$CLIENT_SECRET" \
|
--secret "$CLIENT_SECRET" \
|
||||||
--auto-discover-url "$DISCOVERY_URL" \
|
--auto-discover-url "$DISCOVERY_URL" \
|
||||||
--scopes "openid profile email" \
|
--scopes "openid profile email groups" \
|
||||||
--required-claim-name "" \
|
--required-claim-name "" \
|
||||||
--required-claim-value "" \
|
--required-claim-value "" \
|
||||||
--group-claim-name groups \
|
--group-claim-name groups \
|
||||||
@ -77,7 +77,7 @@ spec:
|
|||||||
--key "$CLIENT_ID" \
|
--key "$CLIENT_ID" \
|
||||||
--secret "$CLIENT_SECRET" \
|
--secret "$CLIENT_SECRET" \
|
||||||
--auto-discover-url "$DISCOVERY_URL" \
|
--auto-discover-url "$DISCOVERY_URL" \
|
||||||
--scopes "openid profile email" \
|
--scopes "openid profile email groups" \
|
||||||
--required-claim-name "" \
|
--required-claim-name "" \
|
||||||
--required-claim-value "" \
|
--required-claim-value "" \
|
||||||
--group-claim-name groups \
|
--group-claim-name groups \
|
||||||
|
|||||||
@ -52,7 +52,7 @@ spec:
|
|||||||
- name: zot-regcred
|
- name: zot-regcred
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: mailu-http-listener
|
- name: mailu-http-listener
|
||||||
image: registry.bstein.dev/sso/mailu-http-listener:0.1.0
|
image: cli.registry.bstein.dev/sso/mailu-http-listener:0.1.0
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
command: ["/bin/sh", "-c"]
|
command: ["/bin/sh", "-c"]
|
||||||
args:
|
args:
|
||||||
|
|||||||
@ -39,7 +39,7 @@ spec:
|
|||||||
- operator: Exists
|
- operator: Exists
|
||||||
containers:
|
containers:
|
||||||
- name: dcgm-exporter
|
- name: dcgm-exporter
|
||||||
image: registry.bstein.dev/monitoring/dcgm-exporter:4.4.2-4.7.0-ubuntu22.04
|
image: cli.registry.bstein.dev/monitoring/dcgm-exporter:4.4.2-4.7.0-ubuntu22.04
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
ports:
|
ports:
|
||||||
- name: metrics
|
- name: metrics
|
||||||
|
|||||||
@ -6,7 +6,7 @@ metadata:
|
|||||||
namespace: sso
|
namespace: sso
|
||||||
spec:
|
spec:
|
||||||
forwardAuth:
|
forwardAuth:
|
||||||
address: http://oauth2-proxy.sso.svc.cluster.local:4180/oauth2/auth
|
address: http://oauth2-proxy.sso.svc.cluster.local/oauth2/auth
|
||||||
trustForwardHeader: true
|
trustForwardHeader: true
|
||||||
authResponseHeaders:
|
authResponseHeaders:
|
||||||
- Authorization
|
- Authorization
|
||||||
|
|||||||
@ -58,7 +58,7 @@ spec:
|
|||||||
|
|
||||||
containers:
|
containers:
|
||||||
- name: pegasus
|
- name: pegasus
|
||||||
image: registry.bstein.dev/pegasus:1.2.32 # {"$imagepolicy": "jellyfin:pegasus"}
|
image: cli.registry.bstein.dev/pegasus:1.2.32 # {"$imagepolicy": "jellyfin:pegasus"}
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
command: ["/pegasus"]
|
command: ["/pegasus"]
|
||||||
env:
|
env:
|
||||||
|
|||||||
@ -5,7 +5,7 @@ metadata:
|
|||||||
name: pegasus
|
name: pegasus
|
||||||
namespace: jellyfin
|
namespace: jellyfin
|
||||||
spec:
|
spec:
|
||||||
image: registry.bstein.dev/pegasus
|
image: cli.registry.bstein.dev/pegasus
|
||||||
interval: 1m0s
|
interval: 1m0s
|
||||||
secretRef:
|
secretRef:
|
||||||
name: zot-regcred
|
name: zot-regcred
|
||||||
|
|||||||
@ -26,14 +26,14 @@ data:
|
|||||||
"repositories": {
|
"repositories": {
|
||||||
"**": {
|
"**": {
|
||||||
"policies": [
|
"policies": [
|
||||||
{ "users": ["bstein"], "actions": ["read", "create", "update", "delete"] }
|
{ "users": ["bstein", "zot-ui-proxy"], "actions": ["read", "create", "update", "delete"] }
|
||||||
],
|
],
|
||||||
"defaultPolicy": [],
|
"defaultPolicy": [],
|
||||||
"anonymousPolicy": []
|
"anonymousPolicy": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"adminPolicy": {
|
"adminPolicy": {
|
||||||
"users": ["bstein"],
|
"users": ["bstein", "zot-ui-proxy"],
|
||||||
"actions": ["read", "create", "update", "delete"]
|
"actions": ["read", "create", "update", "delete"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,6 +35,9 @@ spec:
|
|||||||
image: ghcr.io/project-zot/zot-linux-arm64:v2.1.8
|
image: ghcr.io/project-zot/zot-linux-arm64:v2.1.8
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
args: ["serve", "/etc/zot/config.json"]
|
args: ["serve", "/etc/zot/config.json"]
|
||||||
|
env:
|
||||||
|
- name: UI_PROXY_HTPASSWD
|
||||||
|
value: "zot-ui-proxy:$2y$05$ctfbLo5KBoNA6pluLGGWde6TK8eOPnIH9u8x/IivAhcE/k0qCCR3y"
|
||||||
ports:
|
ports:
|
||||||
- { name: http, containerPort: 5000 }
|
- { name: http, containerPort: 5000 }
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
@ -45,7 +48,6 @@ spec:
|
|||||||
- name: htpasswd
|
- name: htpasswd
|
||||||
mountPath: /etc/zot/htpasswd
|
mountPath: /etc/zot/htpasswd
|
||||||
subPath: htpasswd
|
subPath: htpasswd
|
||||||
readOnly: true
|
|
||||||
- name: zot-data
|
- name: zot-data
|
||||||
mountPath: /var/lib/registry
|
mountPath: /var/lib/registry
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
@ -60,13 +62,41 @@ spec:
|
|||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
resources:
|
resources:
|
||||||
requests: { cpu: "50m", memory: "64Mi" }
|
requests: { cpu: "50m", memory: "64Mi" }
|
||||||
|
initContainers:
|
||||||
|
- name: merge-htpasswd
|
||||||
|
image: busybox:1.36
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
if [ -f /src/htpasswd ]; then
|
||||||
|
cp /src/htpasswd /merged/htpasswd
|
||||||
|
else
|
||||||
|
touch /merged/htpasswd
|
||||||
|
fi
|
||||||
|
if [ -n "${UI_PROXY_HTPASSWD}" ]; then
|
||||||
|
echo "${UI_PROXY_HTPASSWD}" >> /merged/htpasswd
|
||||||
|
fi
|
||||||
|
env:
|
||||||
|
- name: UI_PROXY_HTPASSWD
|
||||||
|
value: "zot-ui-proxy:$2y$05$ctfbLo5KBoNA6pluLGGWde6TK8eOPnIH9u8x/IivAhcE/k0qCCR3y"
|
||||||
|
volumeMounts:
|
||||||
|
- name: htpasswd-source
|
||||||
|
mountPath: /src
|
||||||
|
readOnly: true
|
||||||
|
- name: htpasswd
|
||||||
|
mountPath: /merged
|
||||||
volumes:
|
volumes:
|
||||||
- name: cfg
|
- name: cfg
|
||||||
configMap:
|
configMap:
|
||||||
name: zot-config
|
name: zot-config
|
||||||
- name: htpasswd
|
- name: htpasswd-source
|
||||||
secret:
|
secret:
|
||||||
secretName: zot-htpasswd
|
secretName: zot-htpasswd
|
||||||
|
optional: true
|
||||||
|
- name: htpasswd
|
||||||
|
emptyDir: {}
|
||||||
- name: zot-data
|
- name: zot-data
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: zot-data
|
claimName: zot-data
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
apiVersion: networking.k8s.io/v1
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: zot
|
name: zot-cli
|
||||||
namespace: zot
|
namespace: zot
|
||||||
annotations:
|
annotations:
|
||||||
cert-manager.io/cluster-issuer: letsencrypt
|
cert-manager.io/cluster-issuer: letsencrypt
|
||||||
@ -12,10 +12,10 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
ingressClassName: traefik
|
ingressClassName: traefik
|
||||||
tls:
|
tls:
|
||||||
- hosts: [ "registry.bstein.dev" ]
|
- hosts: [ "cli.registry.bstein.dev" ]
|
||||||
secretName: registry-bstein-dev-tls
|
secretName: cli-registry-bstein-dev-tls
|
||||||
rules:
|
rules:
|
||||||
- host: registry.bstein.dev
|
- host: cli.registry.bstein.dev
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
- path: /
|
- path: /
|
||||||
@ -25,3 +25,30 @@ spec:
|
|||||||
name: zot
|
name: zot
|
||||||
port:
|
port:
|
||||||
number: 5000
|
number: 5000
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: zot-ui
|
||||||
|
namespace: zot
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
|
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||||
|
traefik.ingress.kubernetes.io/router.middlewares: zot-zot-ui-auth-header@kubernetescrd, zot-zot-resp-headers@kubernetescrd
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
tls:
|
||||||
|
- hosts: [ "web.registry.bstein.dev" ]
|
||||||
|
secretName: web-registry-bstein-dev-tls
|
||||||
|
rules:
|
||||||
|
- host: web.registry.bstein.dev
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: zot-oauth2-proxy
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
|||||||
@ -7,5 +7,8 @@ resources:
|
|||||||
- deployment.yaml
|
- deployment.yaml
|
||||||
- configmap.yaml
|
- configmap.yaml
|
||||||
- service.yaml
|
- service.yaml
|
||||||
|
- oauth2-proxy-deployment.yaml
|
||||||
|
- oauth2-proxy-service.yaml
|
||||||
- ingress.yaml
|
- ingress.yaml
|
||||||
- middleware.yaml
|
- middleware.yaml
|
||||||
|
- middleware-ui.yaml
|
||||||
|
|||||||
10
services/zot/middleware-ui.yaml
Normal file
10
services/zot/middleware-ui.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# services/zot/middleware-ui.yaml
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: zot-ui-auth-header
|
||||||
|
namespace: zot
|
||||||
|
spec:
|
||||||
|
headers:
|
||||||
|
customRequestHeaders:
|
||||||
|
Authorization: "Basic em90LXVpLXByb3h5OlRlbXBTc29VaVBhc3MhMjAyNQ=="
|
||||||
83
services/zot/oauth2-proxy-deployment.yaml
Normal file
83
services/zot/oauth2-proxy-deployment.yaml
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# services/zot/oauth2-proxy-deployment.yaml
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: zot-oauth2-proxy
|
||||||
|
namespace: zot
|
||||||
|
labels: { app: zot-oauth2-proxy }
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels: { app: zot-oauth2-proxy }
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels: { app: zot-oauth2-proxy }
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
node-role.kubernetes.io/worker: "true"
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- weight: 50
|
||||||
|
preference:
|
||||||
|
matchExpressions:
|
||||||
|
- key: hardware
|
||||||
|
operator: In
|
||||||
|
values: ["rpi4","rpi5"]
|
||||||
|
containers:
|
||||||
|
- name: oauth2-proxy
|
||||||
|
image: quay.io/oauth2-proxy/oauth2-proxy:v7.6.0
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
args:
|
||||||
|
- --provider=oidc
|
||||||
|
- --redirect-url=https://web.registry.bstein.dev/oauth2/callback
|
||||||
|
- --oidc-issuer-url=https://sso.bstein.dev/realms/atlas
|
||||||
|
- --scope=openid profile email
|
||||||
|
- --email-domain=*
|
||||||
|
- --cookie-domain=web.registry.bstein.dev
|
||||||
|
- --cookie-name=_zot_ui_oauth
|
||||||
|
- --set-xauthrequest=true
|
||||||
|
- --set-authorization-header=false
|
||||||
|
- --pass-authorization-header=false
|
||||||
|
- --pass-access-token=false
|
||||||
|
- --cookie-secure=true
|
||||||
|
- --cookie-samesite=lax
|
||||||
|
- --cookie-refresh=20m
|
||||||
|
- --cookie-expire=168h
|
||||||
|
- --upstream=http://zot:5000
|
||||||
|
- --http-address=0.0.0.0:4180
|
||||||
|
- --skip-provider-button=true
|
||||||
|
- --skip-jwt-bearer-tokens=true
|
||||||
|
env:
|
||||||
|
- name: OAUTH2_PROXY_CLIENT_ID
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: zot-oidc
|
||||||
|
key: client_id
|
||||||
|
- name: OAUTH2_PROXY_CLIENT_SECRET
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: zot-oidc
|
||||||
|
key: client_secret
|
||||||
|
- name: OAUTH2_PROXY_COOKIE_SECRET
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: zot-oidc
|
||||||
|
key: client_secret
|
||||||
|
ports:
|
||||||
|
- containerPort: 4180
|
||||||
|
name: http
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /ping
|
||||||
|
port: 4180
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /ping
|
||||||
|
port: 4180
|
||||||
|
initialDelaySeconds: 20
|
||||||
|
periodSeconds: 20
|
||||||
|
resources:
|
||||||
|
requests: { cpu: "25m", memory: "64Mi" }
|
||||||
14
services/zot/oauth2-proxy-service.yaml
Normal file
14
services/zot/oauth2-proxy-service.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# services/zot/oauth2-proxy-service.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: zot-oauth2-proxy
|
||||||
|
namespace: zot
|
||||||
|
labels: { app: zot-oauth2-proxy }
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector: { app: zot-oauth2-proxy }
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 4180
|
||||||
Loading…
x
Reference in New Issue
Block a user