249 lines
6.5 KiB
Bash
249 lines
6.5 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
authentik_url="${AUTHENTIK_URL:-https://auth.burrow.net}"
|
|
bootstrap_token="${AUTHENTIK_BOOTSTRAP_TOKEN:-}"
|
|
directory_json="${AUTHENTIK_BURROW_DIRECTORY_JSON:-[]}"
|
|
users_group="${AUTHENTIK_BURROW_USERS_GROUP:-burrow-users}"
|
|
admins_group="${AUTHENTIK_BURROW_ADMINS_GROUP:-burrow-admins}"
|
|
forgejo_application_slug="${AUTHENTIK_FORGEJO_APPLICATION_SLUG:-}"
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: Scripts/authentik-sync-burrow-directory.sh
|
|
|
|
Required environment:
|
|
AUTHENTIK_BOOTSTRAP_TOKEN
|
|
AUTHENTIK_BURROW_DIRECTORY_JSON
|
|
|
|
Optional environment:
|
|
AUTHENTIK_URL
|
|
AUTHENTIK_BURROW_USERS_GROUP
|
|
AUTHENTIK_BURROW_ADMINS_GROUP
|
|
AUTHENTIK_FORGEJO_APPLICATION_SLUG
|
|
EOF
|
|
}
|
|
|
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
usage
|
|
exit 0
|
|
fi
|
|
|
|
if [[ -z "$bootstrap_token" ]]; then
|
|
echo "error: AUTHENTIK_BOOTSTRAP_TOKEN is required" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! printf '%s' "$directory_json" | jq -e 'type == "array"' >/dev/null; then
|
|
echo "error: AUTHENTIK_BURROW_DIRECTORY_JSON must be a JSON array" >&2
|
|
exit 1
|
|
fi
|
|
|
|
api() {
|
|
local method="$1"
|
|
local path="$2"
|
|
local data="${3:-}"
|
|
|
|
if [[ -n "$data" ]]; then
|
|
curl -fsS \
|
|
-X "$method" \
|
|
-H "Authorization: Bearer ${bootstrap_token}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$data" \
|
|
"${authentik_url}${path}"
|
|
else
|
|
curl -fsS \
|
|
-X "$method" \
|
|
-H "Authorization: Bearer ${bootstrap_token}" \
|
|
"${authentik_url}${path}"
|
|
fi
|
|
}
|
|
|
|
wait_for_authentik() {
|
|
for _ in $(seq 1 90); do
|
|
if curl -fsS "${authentik_url}/-/health/ready/" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
sleep 2
|
|
done
|
|
echo "error: Authentik did not become ready at ${authentik_url}" >&2
|
|
exit 1
|
|
}
|
|
|
|
lookup_group_pk() {
|
|
local group_name="$1"
|
|
|
|
api GET "/api/v3/core/groups/?page_size=200&search=${group_name}" \
|
|
| jq -r --arg name "$group_name" '.results[]? | select(.name == $name) | .pk // empty' \
|
|
| head -n1
|
|
}
|
|
|
|
ensure_group() {
|
|
local group_name="$1"
|
|
local payload group_pk
|
|
|
|
payload="$(
|
|
jq -cn \
|
|
--arg name "$group_name" \
|
|
'{name: $name}'
|
|
)"
|
|
|
|
group_pk="$(lookup_group_pk "$group_name")"
|
|
if [[ -n "$group_pk" ]]; then
|
|
api PATCH "/api/v3/core/groups/${group_pk}/" "$payload" >/dev/null
|
|
else
|
|
group_pk="$(
|
|
api POST "/api/v3/core/groups/" "$payload" \
|
|
| jq -r '.pk // empty'
|
|
)"
|
|
fi
|
|
|
|
if [[ -z "$group_pk" ]]; then
|
|
echo "error: could not create Authentik group ${group_name}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
printf '%s\n' "$group_pk"
|
|
}
|
|
|
|
lookup_user_pk() {
|
|
local username="$1"
|
|
|
|
api GET "/api/v3/core/users/?page_size=200&search=${username}" \
|
|
| jq -r --arg username "$username" '.results[]? | select(.username == $username) | .pk // empty' \
|
|
| head -n1
|
|
}
|
|
|
|
ensure_user() {
|
|
local user_spec="$1"
|
|
local username name email is_admin groups_json effective_groups_json group_name
|
|
local group_pks_json payload user_pk
|
|
|
|
username="$(printf '%s\n' "$user_spec" | jq -r '.username')"
|
|
name="$(printf '%s\n' "$user_spec" | jq -r '.name')"
|
|
email="$(printf '%s\n' "$user_spec" | jq -r '.email')"
|
|
is_admin="$(printf '%s\n' "$user_spec" | jq -r '.isAdmin // false')"
|
|
groups_json="$(printf '%s\n' "$user_spec" | jq -c '.groups // []')"
|
|
|
|
if [[ -z "$username" || "$username" == "null" || -z "$email" || "$email" == "null" ]]; then
|
|
echo "error: each Burrow Authentik user requires username and email" >&2
|
|
exit 1
|
|
fi
|
|
|
|
effective_groups_json="$(
|
|
printf '%s\n' "$groups_json" \
|
|
| jq -c --arg users_group "$users_group" --arg admins_group "$admins_group" --argjson is_admin "$is_admin" '
|
|
. + [$users_group] + (if $is_admin then [$admins_group] else [] end) | unique
|
|
'
|
|
)"
|
|
|
|
group_pks_json='[]'
|
|
while IFS= read -r group_name; do
|
|
group_pk="$(ensure_group "$group_name")"
|
|
group_pks_json="$(
|
|
jq -cn \
|
|
--argjson current "$group_pks_json" \
|
|
--arg next "$group_pk" \
|
|
'$current + [$next]'
|
|
)"
|
|
done < <(printf '%s\n' "$effective_groups_json" | jq -r '.[]')
|
|
|
|
payload="$(
|
|
jq -cn \
|
|
--arg username "$username" \
|
|
--arg name "$name" \
|
|
--arg email "$email" \
|
|
--argjson groups "$group_pks_json" \
|
|
'{
|
|
username: $username,
|
|
name: $name,
|
|
email: $email,
|
|
is_active: true,
|
|
path: "users",
|
|
groups: $groups
|
|
}'
|
|
)"
|
|
|
|
user_pk="$(lookup_user_pk "$username")"
|
|
if [[ -n "$user_pk" ]]; then
|
|
api PATCH "/api/v3/core/users/${user_pk}/" "$payload" >/dev/null
|
|
else
|
|
user_pk="$(
|
|
api POST "/api/v3/core/users/" "$payload" \
|
|
| jq -r '.pk // empty'
|
|
)"
|
|
fi
|
|
|
|
if [[ -z "$user_pk" ]]; then
|
|
echo "error: could not create Authentik user ${username}" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
lookup_application_pk() {
|
|
local slug="$1"
|
|
|
|
api GET "/api/v3/core/applications/?page_size=200" \
|
|
| jq -r --arg slug "$slug" '.results[]? | select(.slug == $slug) | .pk // empty' \
|
|
| head -n1
|
|
}
|
|
|
|
ensure_application_group_binding() {
|
|
local application_slug="$1"
|
|
local group_name="$2"
|
|
local application_pk group_pk existing payload binding_pk
|
|
|
|
application_pk="$(lookup_application_pk "$application_slug")"
|
|
if [[ -z "$application_pk" ]]; then
|
|
echo "warning: could not resolve Authentik application ${application_slug}; skipping application group binding" >&2
|
|
return 0
|
|
fi
|
|
|
|
group_pk="$(lookup_group_pk "$group_name")"
|
|
if [[ -z "$group_pk" ]]; then
|
|
echo "error: could not resolve Authentik group ${group_name}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
existing="$(
|
|
api GET "/api/v3/policies/bindings/?page_size=200&target=${application_pk}" \
|
|
| jq -c --arg group_pk "$group_pk" '.results[]? | select(.group == $group_pk)' \
|
|
| head -n1
|
|
)"
|
|
|
|
payload="$(
|
|
jq -cn \
|
|
--arg target "$application_pk" \
|
|
--arg group "$group_pk" \
|
|
'{
|
|
group: $group,
|
|
target: $target,
|
|
negate: false,
|
|
enabled: true,
|
|
order: 100,
|
|
timeout: 30,
|
|
failure_result: false
|
|
}'
|
|
)"
|
|
|
|
if [[ -n "$existing" ]]; then
|
|
binding_pk="$(printf '%s\n' "$existing" | jq -r '.pk')"
|
|
api PATCH "/api/v3/policies/bindings/${binding_pk}/" "$payload" >/dev/null
|
|
else
|
|
api POST "/api/v3/policies/bindings/" "$payload" >/dev/null
|
|
fi
|
|
}
|
|
|
|
wait_for_authentik
|
|
ensure_group "$users_group" >/dev/null
|
|
ensure_group "$admins_group" >/dev/null
|
|
|
|
while IFS= read -r user_spec; do
|
|
ensure_user "$user_spec"
|
|
done < <(printf '%s\n' "$directory_json" | jq -c '.[]')
|
|
|
|
if [[ -n "$forgejo_application_slug" ]]; then
|
|
ensure_application_group_binding "$forgejo_application_slug" "$users_group"
|
|
fi
|
|
|
|
echo "Synced Burrow Authentik directory."
|