Add Authentik SSO apps for Linear and 1Password
This commit is contained in:
parent
5a4fe58b86
commit
4d3257995b
5 changed files with 885 additions and 0 deletions
243
Scripts/authentik-sync-1password-oidc.sh
Executable file
243
Scripts/authentik-sync-1password-oidc.sh
Executable file
|
|
@ -0,0 +1,243 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
authentik_url="${AUTHENTIK_URL:-https://auth.burrow.net}"
|
||||||
|
bootstrap_token="${AUTHENTIK_BOOTSTRAP_TOKEN:-}"
|
||||||
|
application_slug="${AUTHENTIK_ONEPASSWORD_APPLICATION_SLUG:-onepassword}"
|
||||||
|
application_name="${AUTHENTIK_ONEPASSWORD_APPLICATION_NAME:-1Password}"
|
||||||
|
provider_name="${AUTHENTIK_ONEPASSWORD_PROVIDER_NAME:-1Password}"
|
||||||
|
template_slug="${AUTHENTIK_ONEPASSWORD_TEMPLATE_SLUG:-ts}"
|
||||||
|
client_id="${AUTHENTIK_ONEPASSWORD_CLIENT_ID:-1password.burrow.net}"
|
||||||
|
launch_url="${AUTHENTIK_ONEPASSWORD_LAUNCH_URL:-https://burrow-team.1password.com/}"
|
||||||
|
redirect_uris_json="${AUTHENTIK_ONEPASSWORD_REDIRECT_URIS_JSON:-[
|
||||||
|
\"https://burrow-team.1password.com/sso/oidc/redirect/\",
|
||||||
|
\"onepassword://sso/oidc/redirect\"
|
||||||
|
]}"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage: Scripts/authentik-sync-1password-oidc.sh
|
||||||
|
|
||||||
|
Required environment:
|
||||||
|
AUTHENTIK_BOOTSTRAP_TOKEN
|
||||||
|
|
||||||
|
Optional environment:
|
||||||
|
AUTHENTIK_URL
|
||||||
|
AUTHENTIK_ONEPASSWORD_APPLICATION_SLUG
|
||||||
|
AUTHENTIK_ONEPASSWORD_APPLICATION_NAME
|
||||||
|
AUTHENTIK_ONEPASSWORD_PROVIDER_NAME
|
||||||
|
AUTHENTIK_ONEPASSWORD_TEMPLATE_SLUG
|
||||||
|
AUTHENTIK_ONEPASSWORD_CLIENT_ID
|
||||||
|
AUTHENTIK_ONEPASSWORD_LAUNCH_URL
|
||||||
|
AUTHENTIK_ONEPASSWORD_REDIRECT_URIS_JSON
|
||||||
|
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' "$redirect_uris_json" | jq -e 'type == "array" and length > 0' >/dev/null; then
|
||||||
|
echo "error: AUTHENTIK_ONEPASSWORD_REDIRECT_URIS_JSON must be a non-empty 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
|
||||||
|
}
|
||||||
|
|
||||||
|
api_with_status() {
|
||||||
|
local method="$1"
|
||||||
|
local path="$2"
|
||||||
|
local data="${3:-}"
|
||||||
|
local response_file status
|
||||||
|
|
||||||
|
response_file="$(mktemp)"
|
||||||
|
trap 'rm -f "$response_file"' RETURN
|
||||||
|
|
||||||
|
if [[ -n "$data" ]]; then
|
||||||
|
status="$(
|
||||||
|
curl -sS \
|
||||||
|
-o "$response_file" \
|
||||||
|
-w '%{http_code}' \
|
||||||
|
-X "$method" \
|
||||||
|
-H "Authorization: Bearer ${bootstrap_token}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$data" \
|
||||||
|
"${authentik_url}${path}"
|
||||||
|
)"
|
||||||
|
else
|
||||||
|
status="$(
|
||||||
|
curl -sS \
|
||||||
|
-o "$response_file" \
|
||||||
|
-w '%{http_code}' \
|
||||||
|
-X "$method" \
|
||||||
|
-H "Authorization: Bearer ${bootstrap_token}" \
|
||||||
|
"${authentik_url}${path}"
|
||||||
|
)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "$status"
|
||||||
|
cat "$response_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_authentik
|
||||||
|
|
||||||
|
template_provider="$(
|
||||||
|
api GET "/api/v3/providers/oauth2/?page_size=200" \
|
||||||
|
| jq -c --arg template_slug "$template_slug" '.results[]? | select(.assigned_application_slug == $template_slug)' \
|
||||||
|
| head -n1
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ -z "$template_provider" ]]; then
|
||||||
|
echo "error: could not resolve the Authentik OAuth provider template ${template_slug}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
authorization_flow="$(printf '%s\n' "$template_provider" | jq -r '.authorization_flow')"
|
||||||
|
invalidation_flow="$(printf '%s\n' "$template_provider" | jq -r '.invalidation_flow')"
|
||||||
|
property_mappings="$(printf '%s\n' "$template_provider" | jq -c '.property_mappings')"
|
||||||
|
signing_key="$(printf '%s\n' "$template_provider" | jq -r '.signing_key')"
|
||||||
|
|
||||||
|
provider_payload="$(
|
||||||
|
jq -n \
|
||||||
|
--arg name "$provider_name" \
|
||||||
|
--arg authorization_flow "$authorization_flow" \
|
||||||
|
--arg invalidation_flow "$invalidation_flow" \
|
||||||
|
--arg client_id "$client_id" \
|
||||||
|
--arg signing_key "$signing_key" \
|
||||||
|
--argjson property_mappings "$property_mappings" \
|
||||||
|
--argjson redirect_uris "$redirect_uris_json" \
|
||||||
|
'{
|
||||||
|
name: $name,
|
||||||
|
authorization_flow: $authorization_flow,
|
||||||
|
invalidation_flow: $invalidation_flow,
|
||||||
|
client_type: "public",
|
||||||
|
client_id: $client_id,
|
||||||
|
include_claims_in_id_token: true,
|
||||||
|
redirect_uris: ($redirect_uris | map({matching_mode: "strict", url: .})),
|
||||||
|
property_mappings: $property_mappings,
|
||||||
|
signing_key: $signing_key,
|
||||||
|
issuer_mode: "per_provider",
|
||||||
|
sub_mode: "hashed_user_id"
|
||||||
|
}'
|
||||||
|
)"
|
||||||
|
|
||||||
|
existing_provider="$(
|
||||||
|
api GET "/api/v3/providers/oauth2/?page_size=200" \
|
||||||
|
| jq -c \
|
||||||
|
--arg application_slug "$application_slug" \
|
||||||
|
--arg provider_name "$provider_name" \
|
||||||
|
'.results[]? | select(.assigned_application_slug == $application_slug or .name == $provider_name)' \
|
||||||
|
| head -n1
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ -n "$existing_provider" ]]; then
|
||||||
|
provider_pk="$(printf '%s\n' "$existing_provider" | jq -r '.pk')"
|
||||||
|
api PATCH "/api/v3/providers/oauth2/${provider_pk}/" "$provider_payload" >/dev/null
|
||||||
|
else
|
||||||
|
provider_pk="$(
|
||||||
|
api POST "/api/v3/providers/oauth2/" "$provider_payload" \
|
||||||
|
| jq -r '.pk // empty'
|
||||||
|
)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${provider_pk:-}" ]]; then
|
||||||
|
echo "error: 1Password OIDC provider did not return a primary key" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
application_payload="$(
|
||||||
|
jq -n \
|
||||||
|
--arg name "$application_name" \
|
||||||
|
--arg slug "$application_slug" \
|
||||||
|
--arg provider "$provider_pk" \
|
||||||
|
--arg launch_url "$launch_url" \
|
||||||
|
'{
|
||||||
|
name: $name,
|
||||||
|
slug: $slug,
|
||||||
|
provider: ($provider | tonumber),
|
||||||
|
meta_launch_url: $launch_url,
|
||||||
|
open_in_new_tab: true,
|
||||||
|
policy_engine_mode: "any"
|
||||||
|
}'
|
||||||
|
)"
|
||||||
|
|
||||||
|
existing_application="$(
|
||||||
|
api GET "/api/v3/core/applications/?page_size=200" \
|
||||||
|
| jq -c --arg slug "$application_slug" '.results[]? | select(.slug == $slug)' \
|
||||||
|
| head -n1
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ -n "$existing_application" ]]; then
|
||||||
|
application_pk="$(printf '%s\n' "$existing_application" | jq -r '.pk')"
|
||||||
|
else
|
||||||
|
create_application_result="$(
|
||||||
|
api_with_status POST "/api/v3/core/applications/" "$application_payload"
|
||||||
|
)"
|
||||||
|
create_application_status="$(printf '%s\n' "$create_application_result" | sed -n '1p')"
|
||||||
|
create_application_body="$(printf '%s\n' "$create_application_result" | sed '1d')"
|
||||||
|
|
||||||
|
if [[ "$create_application_status" =~ ^20[01]$ ]]; then
|
||||||
|
application_pk="$(printf '%s\n' "$create_application_body" | jq -r '.pk // empty')"
|
||||||
|
elif [[ "$create_application_status" == "400" ]] && printf '%s\n' "$create_application_body" | jq -e '
|
||||||
|
(.slug // [] | index("Application with this slug already exists.")) != null
|
||||||
|
or (.provider // [] | index("Application with this provider already exists.")) != null
|
||||||
|
' >/dev/null; then
|
||||||
|
application_pk="existing-duplicate"
|
||||||
|
else
|
||||||
|
printf '%s\n' "$create_application_body" >&2
|
||||||
|
echo "error: could not reconcile Authentik application ${application_slug}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${application_pk:-}" ]]; then
|
||||||
|
echo "error: 1Password OIDC application did not return a primary key" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for _ in $(seq 1 30); do
|
||||||
|
if curl -fsS "${authentik_url}/application/o/${application_slug}/.well-known/openid-configuration" >/dev/null 2>&1; then
|
||||||
|
echo "Synced Authentik 1Password OIDC application ${application_slug} (${application_name})."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "warning: 1Password OIDC issuer document for ${application_slug} was not immediately readable; keeping reconciled config." >&2
|
||||||
|
echo "Synced Authentik 1Password OIDC application ${application_slug} (${application_name})."
|
||||||
334
Scripts/authentik-sync-linear-saml.sh
Executable file
334
Scripts/authentik-sync-linear-saml.sh
Executable file
|
|
@ -0,0 +1,334 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
authentik_url="${AUTHENTIK_URL:-https://auth.burrow.net}"
|
||||||
|
bootstrap_token="${AUTHENTIK_BOOTSTRAP_TOKEN:-}"
|
||||||
|
application_slug="${AUTHENTIK_LINEAR_APPLICATION_SLUG:-linear}"
|
||||||
|
application_name="${AUTHENTIK_LINEAR_APPLICATION_NAME:-Linear}"
|
||||||
|
provider_name="${AUTHENTIK_LINEAR_PROVIDER_NAME:-Linear}"
|
||||||
|
launch_url="${AUTHENTIK_LINEAR_LAUNCH_URL:-https://linear.app/burrownet}"
|
||||||
|
acs_url="${AUTHENTIK_LINEAR_ACS_URL:-}"
|
||||||
|
audience="${AUTHENTIK_LINEAR_AUDIENCE:-}"
|
||||||
|
issuer="${AUTHENTIK_LINEAR_ISSUER:-${authentik_url}/application/saml/${application_slug}/metadata/}"
|
||||||
|
default_relay_state="${AUTHENTIK_LINEAR_DEFAULT_RELAY_STATE:-}"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage: Scripts/authentik-sync-linear-saml.sh
|
||||||
|
|
||||||
|
Required environment:
|
||||||
|
AUTHENTIK_BOOTSTRAP_TOKEN
|
||||||
|
AUTHENTIK_LINEAR_ACS_URL
|
||||||
|
AUTHENTIK_LINEAR_AUDIENCE
|
||||||
|
|
||||||
|
Optional environment:
|
||||||
|
AUTHENTIK_URL
|
||||||
|
AUTHENTIK_LINEAR_APPLICATION_SLUG
|
||||||
|
AUTHENTIK_LINEAR_APPLICATION_NAME
|
||||||
|
AUTHENTIK_LINEAR_PROVIDER_NAME
|
||||||
|
AUTHENTIK_LINEAR_LAUNCH_URL
|
||||||
|
AUTHENTIK_LINEAR_ISSUER
|
||||||
|
AUTHENTIK_LINEAR_DEFAULT_RELAY_STATE
|
||||||
|
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 [[ -z "$acs_url" ]]; then
|
||||||
|
echo "error: AUTHENTIK_LINEAR_ACS_URL is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$audience" ]]; then
|
||||||
|
echo "error: AUTHENTIK_LINEAR_AUDIENCE is required" >&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
|
||||||
|
}
|
||||||
|
|
||||||
|
api_with_status() {
|
||||||
|
local method="$1"
|
||||||
|
local path="$2"
|
||||||
|
local data="${3:-}"
|
||||||
|
local response_file status
|
||||||
|
|
||||||
|
response_file="$(mktemp)"
|
||||||
|
trap 'rm -f "$response_file"' RETURN
|
||||||
|
|
||||||
|
if [[ -n "$data" ]]; then
|
||||||
|
status="$(
|
||||||
|
curl -sS \
|
||||||
|
-o "$response_file" \
|
||||||
|
-w '%{http_code}' \
|
||||||
|
-X "$method" \
|
||||||
|
-H "Authorization: Bearer ${bootstrap_token}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$data" \
|
||||||
|
"${authentik_url}${path}"
|
||||||
|
)"
|
||||||
|
else
|
||||||
|
status="$(
|
||||||
|
curl -sS \
|
||||||
|
-o "$response_file" \
|
||||||
|
-w '%{http_code}' \
|
||||||
|
-X "$method" \
|
||||||
|
-H "Authorization: Bearer ${bootstrap_token}" \
|
||||||
|
"${authentik_url}${path}"
|
||||||
|
)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "$status"
|
||||||
|
cat "$response_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
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_oauth_template_field() {
|
||||||
|
local field="$1"
|
||||||
|
|
||||||
|
api GET "/api/v3/providers/oauth2/?page_size=200" \
|
||||||
|
| jq -r --arg field "$field" '.results[]? | select(.assigned_application_slug == "ts") | .[$field]' \
|
||||||
|
| head -n1
|
||||||
|
}
|
||||||
|
|
||||||
|
reconcile_property_mapping() {
|
||||||
|
local name="$1"
|
||||||
|
local saml_name="$2"
|
||||||
|
local friendly_name="$3"
|
||||||
|
local expression="$4"
|
||||||
|
local payload existing_pk
|
||||||
|
|
||||||
|
payload="$(
|
||||||
|
jq -n \
|
||||||
|
--arg name "$name" \
|
||||||
|
--arg saml_name "$saml_name" \
|
||||||
|
--arg friendly_name "$friendly_name" \
|
||||||
|
--arg expression "$expression" \
|
||||||
|
'{
|
||||||
|
name: $name,
|
||||||
|
saml_name: $saml_name,
|
||||||
|
friendly_name: $friendly_name,
|
||||||
|
expression: $expression
|
||||||
|
}'
|
||||||
|
)"
|
||||||
|
|
||||||
|
existing_pk="$(
|
||||||
|
api GET "/api/v3/propertymappings/provider/saml/?page_size=200" \
|
||||||
|
| jq -r --arg name "$name" '.results[]? | select(.name == $name) | .pk' \
|
||||||
|
| head -n1
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ -n "$existing_pk" ]]; then
|
||||||
|
api PATCH "/api/v3/propertymappings/provider/saml/${existing_pk}/" "$payload" >/dev/null
|
||||||
|
printf '%s\n' "$existing_pk"
|
||||||
|
else
|
||||||
|
api POST "/api/v3/propertymappings/provider/saml/" "$payload" | jq -r '.pk // empty'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_authentik
|
||||||
|
|
||||||
|
authorization_flow="$(lookup_oauth_template_field authorization_flow)"
|
||||||
|
invalidation_flow="$(lookup_oauth_template_field invalidation_flow)"
|
||||||
|
signing_kp="$(lookup_oauth_template_field signing_key)"
|
||||||
|
|
||||||
|
if [[ -z "$authorization_flow" || -z "$invalidation_flow" || -z "$signing_kp" ]]; then
|
||||||
|
echo "error: could not resolve Authentik provider defaults from Burrow Tailnet template" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
email_mapping_pk="$(
|
||||||
|
reconcile_property_mapping \
|
||||||
|
"Burrow Linear SAML Email" \
|
||||||
|
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" \
|
||||||
|
"email" \
|
||||||
|
'return request.user.email'
|
||||||
|
)"
|
||||||
|
|
||||||
|
name_mapping_pk="$(
|
||||||
|
reconcile_property_mapping \
|
||||||
|
"Burrow Linear SAML Name" \
|
||||||
|
"name" \
|
||||||
|
"name" \
|
||||||
|
'return request.user.name or request.user.username'
|
||||||
|
)"
|
||||||
|
|
||||||
|
first_name_mapping_pk="$(
|
||||||
|
reconcile_property_mapping \
|
||||||
|
"Burrow Linear SAML First Name" \
|
||||||
|
"firstName" \
|
||||||
|
"firstName" \
|
||||||
|
$'parts = (request.user.name or "").split(" ", 1)\nif len(parts) > 0 and parts[0]:\n return parts[0]\nreturn request.user.username'
|
||||||
|
)"
|
||||||
|
|
||||||
|
last_name_mapping_pk="$(
|
||||||
|
reconcile_property_mapping \
|
||||||
|
"Burrow Linear SAML Last Name" \
|
||||||
|
"lastName" \
|
||||||
|
"lastName" \
|
||||||
|
$'parts = (request.user.name or "").rsplit(" ", 1)\nif len(parts) == 2 and parts[1]:\n return parts[1]\nreturn request.user.username'
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ -z "$email_mapping_pk" || -z "$name_mapping_pk" || -z "$first_name_mapping_pk" || -z "$last_name_mapping_pk" ]]; then
|
||||||
|
echo "error: failed to reconcile Linear SAML property mappings" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
provider_payload="$(
|
||||||
|
jq -n \
|
||||||
|
--arg name "$provider_name" \
|
||||||
|
--arg authorization_flow "$authorization_flow" \
|
||||||
|
--arg invalidation_flow "$invalidation_flow" \
|
||||||
|
--arg acs_url "$acs_url" \
|
||||||
|
--arg audience "$audience" \
|
||||||
|
--arg issuer "$issuer" \
|
||||||
|
--arg signing_kp "$signing_kp" \
|
||||||
|
--arg default_relay_state "$default_relay_state" \
|
||||||
|
--arg name_id_mapping "$email_mapping_pk" \
|
||||||
|
--arg email_mapping "$email_mapping_pk" \
|
||||||
|
--arg name_mapping "$name_mapping_pk" \
|
||||||
|
--arg first_name_mapping "$first_name_mapping_pk" \
|
||||||
|
--arg last_name_mapping "$last_name_mapping_pk" \
|
||||||
|
'{
|
||||||
|
name: $name,
|
||||||
|
authorization_flow: $authorization_flow,
|
||||||
|
invalidation_flow: $invalidation_flow,
|
||||||
|
acs_url: $acs_url,
|
||||||
|
audience: $audience,
|
||||||
|
issuer: $issuer,
|
||||||
|
signing_kp: $signing_kp,
|
||||||
|
sign_assertion: true,
|
||||||
|
sign_response: true,
|
||||||
|
sp_binding: "post",
|
||||||
|
name_id_mapping: $name_id_mapping,
|
||||||
|
property_mappings: [
|
||||||
|
$email_mapping,
|
||||||
|
$name_mapping,
|
||||||
|
$first_name_mapping,
|
||||||
|
$last_name_mapping
|
||||||
|
]
|
||||||
|
}
|
||||||
|
+ (if $default_relay_state == "" then {} else {default_relay_state: $default_relay_state} end)'
|
||||||
|
)"
|
||||||
|
|
||||||
|
existing_provider="$(
|
||||||
|
api GET "/api/v3/providers/saml/?page_size=200" \
|
||||||
|
| jq -c \
|
||||||
|
--arg application_slug "$application_slug" \
|
||||||
|
--arg provider_name "$provider_name" \
|
||||||
|
'.results[]? | select(.assigned_application_slug == $application_slug or .name == $provider_name)' \
|
||||||
|
| head -n1
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ -n "$existing_provider" ]]; then
|
||||||
|
provider_pk="$(printf '%s\n' "$existing_provider" | jq -r '.pk')"
|
||||||
|
api PATCH "/api/v3/providers/saml/${provider_pk}/" "$provider_payload" >/dev/null
|
||||||
|
else
|
||||||
|
provider_pk="$(
|
||||||
|
api POST "/api/v3/providers/saml/" "$provider_payload" \
|
||||||
|
| jq -r '.pk // empty'
|
||||||
|
)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${provider_pk:-}" ]]; then
|
||||||
|
echo "error: Linear SAML provider did not return a primary key" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
application_payload="$(
|
||||||
|
jq -n \
|
||||||
|
--arg name "$application_name" \
|
||||||
|
--arg slug "$application_slug" \
|
||||||
|
--arg provider "$provider_pk" \
|
||||||
|
--arg launch_url "$launch_url" \
|
||||||
|
'{
|
||||||
|
name: $name,
|
||||||
|
slug: $slug,
|
||||||
|
provider: ($provider | tonumber),
|
||||||
|
meta_launch_url: $launch_url,
|
||||||
|
open_in_new_tab: true,
|
||||||
|
policy_engine_mode: "any"
|
||||||
|
}'
|
||||||
|
)"
|
||||||
|
|
||||||
|
existing_application="$(
|
||||||
|
api GET "/api/v3/core/applications/?page_size=200" \
|
||||||
|
| jq -c --arg slug "$application_slug" '.results[]? | select(.slug == $slug)' \
|
||||||
|
| head -n1
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ -n "$existing_application" ]]; then
|
||||||
|
application_pk="$(printf '%s\n' "$existing_application" | jq -r '.pk')"
|
||||||
|
api PATCH "/api/v3/core/applications/${application_pk}/" "$application_payload" >/dev/null
|
||||||
|
else
|
||||||
|
create_application_result="$(
|
||||||
|
api_with_status POST "/api/v3/core/applications/" "$application_payload"
|
||||||
|
)"
|
||||||
|
create_application_status="$(printf '%s\n' "$create_application_result" | sed -n '1p')"
|
||||||
|
create_application_body="$(printf '%s\n' "$create_application_result" | sed '1d')"
|
||||||
|
|
||||||
|
if [[ "$create_application_status" =~ ^20[01]$ ]]; then
|
||||||
|
application_pk="$(printf '%s\n' "$create_application_body" | jq -r '.pk // empty')"
|
||||||
|
elif [[ "$create_application_status" == "400" ]] && printf '%s\n' "$create_application_body" | jq -e '
|
||||||
|
(.slug // [] | index("Application with this slug already exists.")) != null
|
||||||
|
or (.provider // [] | index("Application with this provider already exists.")) != null
|
||||||
|
' >/dev/null; then
|
||||||
|
application_pk="existing-duplicate"
|
||||||
|
else
|
||||||
|
printf '%s\n' "$create_application_body" >&2
|
||||||
|
echo "error: could not reconcile Authentik application ${application_slug}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${application_pk:-}" ]]; then
|
||||||
|
echo "error: Linear SAML application did not return a primary key" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for _ in $(seq 1 30); do
|
||||||
|
if curl -fsS "${authentik_url}/application/saml/${application_slug}/metadata/" >/dev/null 2>&1; then
|
||||||
|
echo "Synced Authentik Linear SAML application ${application_slug} (${application_name})."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "warning: Linear SAML metadata for ${application_slug} was not immediately readable; keeping reconciled config." >&2
|
||||||
|
echo "Synced Authentik Linear SAML application ${application_slug} (${application_name})."
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
# `BEP-0008` - Authentik-Backed Team Chat and Workspace Identity
|
||||||
|
|
||||||
|
```text
|
||||||
|
Status: Draft
|
||||||
|
Proposal: BEP-0008
|
||||||
|
Authors: gpt-5.4
|
||||||
|
Coordinator: gpt-5.4
|
||||||
|
Reviewers: Pending
|
||||||
|
Constitution Sections: II, III, V
|
||||||
|
Implementation PRs: Pending
|
||||||
|
Decision Date: Pending
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Burrow should add a self-hosted team chat surface at `chat.burrow.net` and
|
||||||
|
continue the project-wide move toward Authentik as the identity authority for
|
||||||
|
external work systems. The immediate targets are a self-hosted Zulip
|
||||||
|
deployment rooted in Authentik SAML, a Linear SAML configuration when the
|
||||||
|
workspace plan supports it, and a 1Password Unlock-with-SSO deployment rooted
|
||||||
|
in the same Authentik-backed OIDC authority.
|
||||||
|
|
||||||
|
This keeps Burrow's day-to-day coordination surfaces aligned with the same
|
||||||
|
admin groups, canonical users, and secret-handling model already used for
|
||||||
|
Forgejo, Headscale, and Tailscale. It also avoids fragmenting login state
|
||||||
|
across vendor-native Google auth flows when Burrow already operates an IdP.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
- Forge, Tailnet, operator identity, and Tailscale custom OIDC are already
|
||||||
|
rooted in Authentik. Team chat, work tracking, and password-manager access
|
||||||
|
should not become separate authority islands.
|
||||||
|
- Zulip provides a self-hosted chat system under Burrow's control, which fits
|
||||||
|
the constitution better than adding another hosted chat dependency.
|
||||||
|
- Linear remains a SaaS dependency, but its workspace access should still be
|
||||||
|
derived from Burrow-managed identities and domains when the vendor plan
|
||||||
|
exposes SAML configuration.
|
||||||
|
- 1Password Business is another external work surface where Burrow-controlled
|
||||||
|
identities are preferable to vendor-native Google-only auth. Its current
|
||||||
|
vendor flow is OIDC-based Unlock with SSO rather than SAML, so the proposal
|
||||||
|
needs to preserve protocol accuracy instead of flattening everything into
|
||||||
|
one SAML bucket.
|
||||||
|
- Burrow already has a canonical public identity registry and a secret-backed
|
||||||
|
external-email alias map. Reusing that structure is lower-risk than
|
||||||
|
inventing per-app user bootstrap logic.
|
||||||
|
|
||||||
|
## Detailed Design
|
||||||
|
|
||||||
|
- Add a Burrow-managed Zulip workload on the forge host at `chat.burrow.net`.
|
||||||
|
The deployment should be repo-owned and rebuildable from Nix, even if the
|
||||||
|
runtime uses vendor-supported container images internally.
|
||||||
|
- Zulip should authenticate through Authentik SAML rather than local passwords
|
||||||
|
as the primary path. Initial bootstrap may still keep an operational escape
|
||||||
|
hatch while the deployment is being validated.
|
||||||
|
- Add Authentik-managed SAML applications for:
|
||||||
|
- Zulip at `chat.burrow.net`
|
||||||
|
- Linear using Burrow's claimed domains and Authentik metadata
|
||||||
|
- Add an Authentik-managed OIDC application for 1Password Business under the
|
||||||
|
Burrow team sign-in address.
|
||||||
|
- Treat Zulip and Linear as downstream applications of the same identity
|
||||||
|
authority, and treat 1Password as part of that same authority even though
|
||||||
|
its vendor protocol is OIDC rather than SAML. The source of truth remains:
|
||||||
|
- public identities and admin intent in `contributors.nix`
|
||||||
|
- private alias mappings and external accounts in agenix-encrypted secrets
|
||||||
|
- Keep app-specific configuration in dedicated reconciliation code or module
|
||||||
|
options instead of hand-edited UI state.
|
||||||
|
- Prefer service-specific reconciliation over ad hoc manual setup so rebuilds
|
||||||
|
and host replacement converge automatically.
|
||||||
|
- Model 1Password according to the vendor's actual integration contract:
|
||||||
|
- OIDC Authorization Code Flow with PKCE
|
||||||
|
- public client rather than a confidential client
|
||||||
|
- no Burrow-side dependence on a stored client secret unless the vendor flow
|
||||||
|
changes
|
||||||
|
|
||||||
|
## Security and Operational Considerations
|
||||||
|
|
||||||
|
- Do not store external personal email mappings in public registry files.
|
||||||
|
Public tree data may include Burrow usernames and canonical `@burrow.net`
|
||||||
|
addresses, but external aliases must stay in encrypted secrets.
|
||||||
|
- Zulip internal service credentials, Django secret material, and any mail
|
||||||
|
credentials must have explicit storage and rotation paths.
|
||||||
|
- Linear SAML must not become Burrow's only admin recovery path. At least one
|
||||||
|
owner login path outside the enforced SAML flow should remain available until
|
||||||
|
rollout is proven.
|
||||||
|
- 1Password Owners cannot be forced onto Unlock with SSO during initial setup.
|
||||||
|
Burrow should preserve the owner recovery path and treat OIDC rollout as a
|
||||||
|
scoped migration for non-owner users first.
|
||||||
|
- If Zulip is deployed without production-grade outbound email at first, that
|
||||||
|
limitation must be documented and treated as an operational constraint, not a
|
||||||
|
hidden assumption.
|
||||||
|
- Rollback should be straightforward:
|
||||||
|
- disable or stop the Zulip module
|
||||||
|
- remove the Authentik SAML apps
|
||||||
|
- remove the Authentik OIDC app used for 1Password if necessary
|
||||||
|
- leave the underlying Burrow identities unchanged
|
||||||
|
|
||||||
|
## Contributor Playbook
|
||||||
|
|
||||||
|
- Define the app and identity intent in the repository before modifying the
|
||||||
|
forge host.
|
||||||
|
- Add or update Nix modules so `burrow-forge` can rebuild Zulip and the
|
||||||
|
corresponding Authentik SAML configuration from the tree.
|
||||||
|
- Verify:
|
||||||
|
- `chat.burrow.net` serves a working Zulip login surface
|
||||||
|
- Authentik exposes working metadata for Zulip and Linear
|
||||||
|
- Authentik exposes a working OIDC issuer for 1Password
|
||||||
|
- users in Burrow admin groups receive the expected access on first login
|
||||||
|
- Record concrete evidence for:
|
||||||
|
- host deployment generation
|
||||||
|
- Authentik reconciliation success
|
||||||
|
- Zulip login success
|
||||||
|
- Linear SAML configuration state
|
||||||
|
- 1Password Unlock with SSO configuration state
|
||||||
|
|
||||||
|
## Alternatives Considered
|
||||||
|
|
||||||
|
- Use Zulip Cloud instead of self-hosting. Rejected because the ask is to host
|
||||||
|
chat under `chat.burrow.net`, and Burrow already operates a forge host with a
|
||||||
|
self-managed identity plane.
|
||||||
|
- Keep Linear on Google-native login. Rejected because it leaves Burrow work
|
||||||
|
access outside the project's operator and group model.
|
||||||
|
- Treat 1Password as a SAML app for consistency. Rejected because the live
|
||||||
|
vendor flow is OIDC and Burrow should not pretend otherwise in repo-owned
|
||||||
|
infrastructure.
|
||||||
|
- Add per-app manual Authentik configuration without repository automation.
|
||||||
|
Rejected because it violates Burrow's infrastructure-in-repo commitment.
|
||||||
|
|
||||||
|
## Impact on Other Work
|
||||||
|
|
||||||
|
- Extends Burrow's Authentik role from control-plane identity into team-work
|
||||||
|
surfaces.
|
||||||
|
- Introduces a persistent chat workload on the forge host, with resource and
|
||||||
|
monitoring implications.
|
||||||
|
- Creates a likely follow-up for SCIM or richer group synchronization if Linear
|
||||||
|
or Zulip role mapping needs to become fully declarative later.
|
||||||
|
- Adds a second OIDC relying party beyond Forgejo, Headscale, and Tailscale,
|
||||||
|
which raises the importance of keeping Burrow's Authentik scope mappings and
|
||||||
|
redirect handling consistent across applications.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Pending.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- `CONSTITUTION.md`
|
||||||
|
- `contributors.nix`
|
||||||
|
- `evolution/proposals/BEP-0004-hosted-mail-and-saas-identity.md`
|
||||||
|
- Authentik docs: SAML provider and metadata endpoints
|
||||||
|
- Zulip docs: SAML authentication and docker deployment
|
||||||
|
- Linear docs: SAML and access control
|
||||||
|
- 1Password docs: Unlock with SSO using OpenID Connect
|
||||||
|
|
@ -207,6 +207,9 @@ in
|
||||||
userGroupName = contributors.groups.users;
|
userGroupName = contributors.groups.users;
|
||||||
adminGroupName = contributors.groups.admins;
|
adminGroupName = contributors.groups.admins;
|
||||||
bootstrapUsers = bootstrapUsers;
|
bootstrapUsers = bootstrapUsers;
|
||||||
|
linearAcsUrl = "https://api.linear.app/auth/sso/d0ca13dc-ac41-4824-8aab-e0ca352fc3de/acs";
|
||||||
|
linearAudience = "https://auth.linear.app/sso/d0ca13dc-ac41-4824-8aab-e0ca352fc3de";
|
||||||
|
linearDefaultRelayState = "https://linear.app/auth/sso/d0ca13dc-ac41-4824-8aab-e0ca352fc3de";
|
||||||
};
|
};
|
||||||
|
|
||||||
services.burrow.headscale = {
|
services.burrow.headscale = {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ let
|
||||||
directorySyncScript = ../../Scripts/authentik-sync-burrow-directory.sh;
|
directorySyncScript = ../../Scripts/authentik-sync-burrow-directory.sh;
|
||||||
forgejoOidcSyncScript = ../../Scripts/authentik-sync-forgejo-oidc.sh;
|
forgejoOidcSyncScript = ../../Scripts/authentik-sync-forgejo-oidc.sh;
|
||||||
tailscaleOidcSyncScript = ../../Scripts/authentik-sync-tailscale-oidc.sh;
|
tailscaleOidcSyncScript = ../../Scripts/authentik-sync-tailscale-oidc.sh;
|
||||||
|
onePasswordOidcSyncScript = ../../Scripts/authentik-sync-1password-oidc.sh;
|
||||||
|
linearSamlSyncScript = ../../Scripts/authentik-sync-linear-saml.sh;
|
||||||
googleSourceSyncScript = ../../Scripts/authentik-sync-google-source.sh;
|
googleSourceSyncScript = ../../Scripts/authentik-sync-google-source.sh;
|
||||||
tailnetAuthFlowSyncScript = ../../Scripts/authentik-sync-tailnet-auth-flow.sh;
|
tailnetAuthFlowSyncScript = ../../Scripts/authentik-sync-tailnet-auth-flow.sh;
|
||||||
authentikBlueprint = pkgs.writeText "burrow-authentik-blueprint.yaml" ''
|
authentikBlueprint = pkgs.writeText "burrow-authentik-blueprint.yaml" ''
|
||||||
|
|
@ -150,6 +152,63 @@ in
|
||||||
description = "Host-local file containing the Authentik Tailscale OIDC client secret.";
|
description = "Host-local file containing the Authentik Tailscale OIDC client secret.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onePasswordDomain = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "burrow-team.1password.com";
|
||||||
|
description = "1Password team sign-in domain used for Burrow Unlock with SSO.";
|
||||||
|
};
|
||||||
|
|
||||||
|
onePasswordProviderSlug = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "onepassword";
|
||||||
|
description = "Authentik application slug for 1Password Unlock with SSO.";
|
||||||
|
};
|
||||||
|
|
||||||
|
onePasswordClientId = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "1password.burrow.net";
|
||||||
|
description = "Public OIDC client ID Authentik should present to 1Password.";
|
||||||
|
};
|
||||||
|
|
||||||
|
onePasswordRedirectUris = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [
|
||||||
|
"https://burrow-team.1password.com/sso/oidc/redirect/"
|
||||||
|
"onepassword://sso/oidc/redirect"
|
||||||
|
];
|
||||||
|
description = "Allowed 1Password OIDC redirect URIs.";
|
||||||
|
};
|
||||||
|
|
||||||
|
linearProviderSlug = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "linear";
|
||||||
|
description = "Authentik application slug for Linear SAML.";
|
||||||
|
};
|
||||||
|
|
||||||
|
linearAcsUrl = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Linear SAML ACS URL.";
|
||||||
|
};
|
||||||
|
|
||||||
|
linearAudience = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Linear SAML audience/entity identifier.";
|
||||||
|
};
|
||||||
|
|
||||||
|
linearLaunchUrl = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "https://linear.app/burrownet";
|
||||||
|
description = "Linear workspace URL exposed in Authentik.";
|
||||||
|
};
|
||||||
|
|
||||||
|
linearDefaultRelayState = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Optional Linear relay state or login URL for IdP-initiated launches.";
|
||||||
|
};
|
||||||
|
|
||||||
forgejoClientId = lib.mkOption {
|
forgejoClientId = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "git.burrow.net";
|
default = "git.burrow.net";
|
||||||
|
|
@ -718,6 +777,100 @@ EOF
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
systemd.services.burrow-authentik-1password-oidc = {
|
||||||
|
description = "Reconcile the Burrow Authentik 1Password OIDC application";
|
||||||
|
after = [
|
||||||
|
"burrow-authentik-ready.service"
|
||||||
|
"network-online.target"
|
||||||
|
];
|
||||||
|
wants = [
|
||||||
|
"burrow-authentik-ready.service"
|
||||||
|
"network-online.target"
|
||||||
|
];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
restartTriggers = [
|
||||||
|
onePasswordOidcSyncScript
|
||||||
|
cfg.envFile
|
||||||
|
];
|
||||||
|
path = [
|
||||||
|
pkgs.bash
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.curl
|
||||||
|
pkgs.jq
|
||||||
|
];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
User = "root";
|
||||||
|
Group = "root";
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
set -euo pipefail
|
||||||
|
set -a
|
||||||
|
source ${lib.escapeShellArg cfg.envFile}
|
||||||
|
set +a
|
||||||
|
|
||||||
|
export AUTHENTIK_URL=https://${cfg.domain}
|
||||||
|
export AUTHENTIK_ONEPASSWORD_APPLICATION_SLUG=${lib.escapeShellArg cfg.onePasswordProviderSlug}
|
||||||
|
export AUTHENTIK_ONEPASSWORD_APPLICATION_NAME=1Password
|
||||||
|
export AUTHENTIK_ONEPASSWORD_PROVIDER_NAME=1Password
|
||||||
|
export AUTHENTIK_ONEPASSWORD_TEMPLATE_SLUG=${lib.escapeShellArg cfg.headscaleProviderSlug}
|
||||||
|
export AUTHENTIK_ONEPASSWORD_CLIENT_ID=${lib.escapeShellArg cfg.onePasswordClientId}
|
||||||
|
export AUTHENTIK_ONEPASSWORD_LAUNCH_URL=https://${cfg.onePasswordDomain}/
|
||||||
|
export AUTHENTIK_ONEPASSWORD_REDIRECT_URIS_JSON='${builtins.toJSON cfg.onePasswordRedirectUris}'
|
||||||
|
|
||||||
|
${pkgs.bash}/bin/bash ${onePasswordOidcSyncScript}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.burrow-authentik-linear-saml = lib.mkIf (
|
||||||
|
cfg.linearAcsUrl != null && cfg.linearAudience != null
|
||||||
|
) {
|
||||||
|
description = "Reconcile the Burrow Authentik Linear SAML application";
|
||||||
|
after = [
|
||||||
|
"burrow-authentik-ready.service"
|
||||||
|
"network-online.target"
|
||||||
|
];
|
||||||
|
wants = [
|
||||||
|
"burrow-authentik-ready.service"
|
||||||
|
"network-online.target"
|
||||||
|
];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
restartTriggers = [
|
||||||
|
linearSamlSyncScript
|
||||||
|
cfg.envFile
|
||||||
|
];
|
||||||
|
path = [
|
||||||
|
pkgs.bash
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.curl
|
||||||
|
pkgs.jq
|
||||||
|
];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
User = "root";
|
||||||
|
Group = "root";
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
set -euo pipefail
|
||||||
|
set -a
|
||||||
|
source ${lib.escapeShellArg cfg.envFile}
|
||||||
|
set +a
|
||||||
|
|
||||||
|
export AUTHENTIK_URL=https://${cfg.domain}
|
||||||
|
export AUTHENTIK_LINEAR_APPLICATION_SLUG=${lib.escapeShellArg cfg.linearProviderSlug}
|
||||||
|
export AUTHENTIK_LINEAR_APPLICATION_NAME=Linear
|
||||||
|
export AUTHENTIK_LINEAR_PROVIDER_NAME=Linear
|
||||||
|
export AUTHENTIK_LINEAR_ACS_URL=${lib.escapeShellArg cfg.linearAcsUrl}
|
||||||
|
export AUTHENTIK_LINEAR_AUDIENCE=${lib.escapeShellArg cfg.linearAudience}
|
||||||
|
export AUTHENTIK_LINEAR_LAUNCH_URL=${lib.escapeShellArg cfg.linearLaunchUrl}
|
||||||
|
${lib.optionalString (cfg.linearDefaultRelayState != null) ''
|
||||||
|
export AUTHENTIK_LINEAR_DEFAULT_RELAY_STATE=${lib.escapeShellArg cfg.linearDefaultRelayState}
|
||||||
|
''}
|
||||||
|
|
||||||
|
${pkgs.bash}/bin/bash ${linearSamlSyncScript}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
services.caddy.virtualHosts."${cfg.domain}".extraConfig = ''
|
services.caddy.virtualHosts."${cfg.domain}".extraConfig = ''
|
||||||
encode gzip zstd
|
encode gzip zstd
|
||||||
reverse_proxy 127.0.0.1:${toString cfg.port}
|
reverse_proxy 127.0.0.1:${toString cfg.port}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue