Skip to content

Instantly share code, notes, and snippets.

@profound-killah
Last active January 10, 2025 21:48
Show Gist options
  • Select an option

  • Save profound-killah/16759e27640aab773bbbb28c5c496291 to your computer and use it in GitHub Desktop.

Select an option

Save profound-killah/16759e27640aab773bbbb28c5c496291 to your computer and use it in GitHub Desktop.
Authentik - Initial setup of embedded outpost instance and its associated Docker service connection
#!/bin/bash
# This script configures an authentik embedded outpost instance and its associated Docker service connection.
# It performs the following actions:
# 1. Retrieves the UUID of the embedded outpost instance.
# 2. Checks for an existing Docker service connection and updates it if necessary.
# 3. Creates a new Docker service connection if none exists.
# 4. Verifies and updates the authentik host configuration for the outpost instance.
# Usage:
# Run this script with the `setup` argument to execute the configuration process.
# Example: ./1.configure_authentik_host.sh setup
# Requirements:
# - jq installed on the system (for JSON parsing)
# - Valid API token with sufficient permissions to perform the operations.
# API Documentation Links:
# - Outpost Instances:
# - https://docs.goauthentik.io/docs/developer-docs/api/reference/outposts-instances-list
# - Docker Service Connections:
# - https://docs.goauthentik.io/docs/developer-docs/api/reference/outposts-service-connections-docker-list
# - https://docs.goauthentik.io/docs/developer-docs/api/reference/outposts-service-connections-docker-retrieve
# - https://docs.goauthentik.io/docs/developer-docs/api/reference/outposts-service-connections-docker-create
# - Embedded Outpost Update:
# - https://docs.goauthentik.io/docs/developer-docs/api/reference/outposts-instances-retrieve
# - https://docs.goauthentik.io/docs/developer-docs/api/reference/outposts-instances-update
# API authentication
api_base="http://localhost:9000/api/v3"
token='<token>'
auth_header="Authorization: Bearer $token"
# Outpost instance configuration
name='authentik Embedded Outpost'
managed='goauthentik.io/outposts/embedded'
authentik_host="https://auth.<example.com>"
# Service connection configuration
docker_service_name="Local Docker connection"
docker_url="//var/run/docker.sock"
# Help function
function show_help() {
echo "Usage: $0 setup"
echo
echo "Options:"
echo " setup Run the setup process to configure the outpost and Docker service connection."
echo " help Show this help message."
echo
exit 0
}
# Check for parameters
[[ $# -eq 0 ]] && show_help
[[ "$1" != "setup" ]] && { echo "Error: Invalid parameter '$1'. Use '$0 help' for usage information."; exit 1; }
# Retrieve outpost instance UUID
outpost_instance_uuid=$(curl -s -L "${api_base}/outposts/instances/" \
-H "accept: application/json" \
-H "$auth_header" | jq -r '.results[0].pk // empty')
if [[ -z "$outpost_instance_uuid" ]]; then
echo "Failed to retrieve outpost instance UUID."
exit 1
fi
echo "Outpost instance UUID: $outpost_instance_uuid"
# Check for existing Docker service connections
response=$(curl -s -L "${api_base}/outposts/service_connections/docker/" \
-H "accept: application/json" \
-H "$auth_header")
outpost_service_connection_pk=$(echo "$response" | jq -r '.results[0].pk // empty')
existing_url=$(echo "$response" | jq -r '.results[0].url // empty')
if [[ -n "$outpost_service_connection_pk" ]]; then
echo "Found existing Docker service connection PK: $outpost_service_connection_pk"
# Check if the existing URL matches the desired URL
if [[ "$existing_url" != "$docker_url" ]]; then
echo "Existing Docker service connection URL ('$existing_url') does not match the desired URL ('$docker_url'). Updating..."
update_response=$(curl -s -X PUT "${api_base}/outposts/service_connections/docker/${outpost_service_connection_pk}/" \
-H "accept: application/json" \
-H "content-type: application/json" \
-H "$auth_header" \
-d "{
\"name\": \"$docker_service_name\",
\"local\": true,
\"url\": \"$docker_url\",
\"tls_verification\": null,
\"tls_authentication\": null
}")
success=$(echo "$update_response" | jq -r '.pk // empty')
if [[ -n "$success" ]]; then
echo "Docker service connection updated successfully. PK: $success"
else
echo "Failed to update Docker service connection. Response: $update_response"
exit 1
fi
else
echo "Existing Docker service connection URL matches the desired URL. No update needed."
fi
else
echo "No Docker service connections found. Creating a new one..."
response=$(curl -s -X POST "${api_base}/outposts/service_connections/docker/" \
-H "accept: application/json" \
-H "content-type: application/json" \
-H "$auth_header" \
-d "{
\"name\": \"$docker_service_name\",
\"local\": true,
\"url\": \"$docker_url\",
\"tls_verification\": null,
\"tls_authentication\": null
}")
outpost_service_connection_pk=$(echo "$response" | jq -r '.pk // empty')
if [[ -z "$outpost_service_connection_pk" ]]; then
echo "Failed to create Docker service connection. Response: $response"
exit 1
fi
echo "Created new Docker service connection PK: $outpost_service_connection_pk"
fi
# Confirm whether authentik_host matches
current_authentik_host=$(curl -s -L "${api_base}/outposts/instances/${outpost_instance_uuid}/" \
-H "accept: application/json" \
-H "$auth_header" | jq -r '.config.authentik_host // empty')
if [[ "$current_authentik_host" == "$authentik_host" ]]; then
echo "✅ No update required. authentik_host matches the desired value."
else
echo "⚠️ authentik_host does not match. Updating embedded outpost..."
# Full update with detailed configuration
update_outpost_instance_response=$(curl -s -L -X PUT "${api_base}/outposts/instances/${outpost_instance_uuid}/" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "$auth_header" \
-d "{
\"name\": \"${name}\",
\"type\": \"proxy\",
\"providers\": [0],
\"service_connection\": \"${outpost_service_connection_pk}\",
\"config\": {
\"log_level\": \"info\",
\"authentik_host\": \"${authentik_host}\",
\"docker_map_ports\": true,
\"refresh_interval\": \"minutes=5\",
\"kubernetes_replicas\": 1,
\"authentik_host_insecure\": false,
\"kubernetes_namespace\": \"default\"
},
\"managed\": \"${managed}\"
}")
# Check for a successful update
updated_pk=$(echo "$update_outpost_instance_response" | jq -r '.pk // empty')
if [[ -n "$updated_pk" ]]; then
echo "✅ Successfully updated embedded outpost. PK: $updated_pk"
else
echo "❌ Failed to update embedded outpost. Response:"
echo "$update_outpost_instance_response" | jq .
exit 1
fi
fi
# what script do:
# - provision providers
# - set name: Treafik Provider
# - set authorization flow - implicit - done
# - set forward auth - done
# - external host: https://traefik.example.com - done
# - set invalidation flow - done
# - provision applications
# - set name: Traefik App - done per app with custom mapping for group and icon
# - set slug: traefik-app - done
# - set group (preferably from docker label) - quick and dirty done
# - set provider - done
# - ui settings
# - set open in new tab - done
# - set icon - may not be doable because api calls look like they only accept local files to upload instead of updating the meta_icon value.
# - activate/select applications in embedded outpost instance
#
token='<token>'
api_base="http://localhost:9000/api/v3"
auth_header="Authorization: Bearer $token"
domain="<example.com>"
# array of applications
applications=(
"traefik"
"plex"
"ombi"
"not porn"
)
# setup providers
default_provider_authorization_flow="default-provider-authorization-implicit-consent"
default_provider_invalidation_flow="default-provider-invalidation-flow"
provider_authorization_flow_uuid=$(curl -s -L "${api_base}/flows/instances/${default_provider_authorization_flow}/" \
-H 'Accept: application/json' \
-H "$auth_header" | jq .pk)
provider_invalidation_flow_uuid=$(curl -s -L "${api_base}/flows/instances/${default_provider_invalidation_flow}/" \
-H 'Accept: application/json' \
-H "$auth_header" | jq .pk)
# Property mappings names to search
property_mappings=(
"authentik%20default%20OAuth%20Mapping%3A%20Proxy%20outpost"
"authentik%20default%20OAuth%20Mapping%3A%20OpenID%20%27openid%27"
"authentik%20default%20OAuth%20Mapping%3A%20OpenID%20%27email%27"
"authentik%20default%20OAuth%20Mapping%3A%20OpenID%20%27profile%27"
"authentik%20default%20OAuth%20Mapping%3A%20Application%20Entitlements"
)
# Initialize property mappings PK array
property_mapping_pks=()
# Fetch PK for each property mapping
for mapping_name in "${property_mappings[@]}"; do
pk=$(curl -s -L "${api_base}/propertymappings/all/?name=${mapping_name}" \
-H "Accept: application/json" \
-H "$auth_header" | jq -r '.pk // empty')
if [[ -n "$pk" ]]; then
property_mapping_pks+=("$pk")
echo "✅ Found PK for ${mapping_name}: $pk"
else
echo "❌ Failed to fetch PK for ${mapping_name}"
fi
done
# Validate we have all PKs
if [[ ${#property_mapping_pks[@]} -ne ${#property_mappings[@]} ]]; then
echo "❌ Not all property mappings were resolved. Exiting."
# could build out some fuzzy logic to retry the failed mappings, maybe future enhancement
exit 1
fi
# Prepare the final API call data
property_mapping_json=$(printf '"%s",' "${property_mapping_pks[@]}" | sed 's/,$//')
for application in "${applications[@]}"; do
# set provider vars
provider_name="${application} Provider"
provider_internal_host=""
if $application == "ombi"; then
provider_external_host="https://${domain}"
else
provider_external_host="https://${application}.${domain}"
fi
provider_basic_auth="false"
provider_mode="forward_single"
provider_access_token_validity="hours=24"
provider_refresh_token_validity="days=30"
# create provider
echo "Provisioning provider: $provider_name"
create_provider_response=$(curl -s -L "${api_base}/providers/proxy/" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "$auth_header" \
-d "{
\"name\": \"${provider_authorization_flow_uuid}\",
\"authentication_flow\": null,
\"authorization_flow\": \"${provider_authorization_flow_uuid}\",
\"invalidation_flow\": \"${provider_invalidation_flow_uuid}\",
\"property_mappings\": [$property_mapping_json],
\"internal_host\": \"${provider_mode}\",
\"external_host\": \"${provider_authorization_flow_uuid}\",
\"internal_host_ssl_validation\": true,
\"certificate\": null,
\"skip_path_regex\": \"\",
\"basic_auth_enabled\": ${provider_basic_auth},
\"basic_auth_password_attribute\": \"\",
\"basic_auth_user_attribute\": \"\",
\"mode\": \"${provider_mode}\",
\"intercept_header_auth\": true,
\"cookie_domain\": \"\",
\"jwt_federation_sources\": [
0
],
\"jwt_federation_providers\": [
0
],
\"access_token_validity\": \"${provider_access_token_validity}\",
\"refresh_token_validity\": \"${provider_refresh_token_validity}\"
}")
# Check for successful response
updated_pk=$(echo "$create_provider_response" | jq -r '.pk // empty')
if [[ -n "$updated_pk" ]]; then
echo "✅ Successfully updated provider. PK: $updated_pk"
else
echo "❌ Failed to update provider. Response:"
echo "$create_provider_response" | jq .
exit 1
fi
## we're done with the provider, now let's move on to the application
# application group & icon svg filename mappings
## The default syntax for icon svg is ${application}.svg
## if no icon svg name is listed below, the default will be used
# admin
# - traefik - traefik-gopher.svg
# - portainer
# - pgadmin - null
# - adminer - adminerevo.svg
# media
# - plex - plex-white.svg
# - ombi
# arr
# - sonarr
# - radarr
# - prowlarr
# - bazarr
# downloads
# - nzbget - sabnzbd.svg
# - qbit-tv - qbittorrent.svg
# - qbit-mv - qbittorrent.svg
# - qbit-misc - qbittorrent.svg
# monitoring
# - grafana
# - prometheus
# - alertmanager - prometheus.svg
# - tautulli
# - apcupsd - apc.svg
# tools
# - kiwix
# - mediawiki - mediawiki-large.svg
# - ittools
# games
# - factorio - null
# - palworld - null
# set application vars
application_group=""
application_name="${application} App"
application_slug="${application}-app"
application_provider_pk="$updated_pk"
application_new_tab="true"
application_icon_path="${application}.svg"
# select case for application icon - if svg reference is null, default will be used
case "$application" in
"traefik")
application_group="admin"
application_icon_path="traefik-gopher.svg"
;;
"plex")
application_group="media"
application_icon_path="plex-white.svg"
;;
"ombi")
application_group="media"
application_icon_path="cacti.svg"
;;
"sonarr")
application_group="arr"
;;
"radarr")
application_group="arr"
;;
"prowlarr")
application_group="arr"
;;
"bazarr")
application_group="arr"
;;
"nzbget")
application_group="downloads"
application_icon_path="sabnzbd.svg"
;;
"qbit-tv")
application_group="downloads"
application_icon_path="qbittorrent.svg"
;;
"qbit-mv")
application_group="downloads"
application_icon_path="qbittorrent.svg"
;;
"qbit-misc")
application_group="downloads"
application_icon_path="qbittorrent.svg"
;;
"grafana")
application_group="monitoring"
;;
"prometheus")
application_group="monitoring"
;;
"alertmanager")
application_group="monitoring"
application_icon_path="prometheus.svg"
;;
"tautulli")
application_group="monitoring"
;;
"apcupsd")
application_group="monitoring"
application_icon_path="apc.svg"
;;
"kiwix")
application_group="tools"
;;
"mediawiki")
application_group="tools"
application_icon_path="mediawiki-large.svg"
;;
"ittools")
application_group="tools"
application_icon_path="cacti.svg"
;;
"factorio")
application_group="games"
application_icon_path="cacti.svg"
;;
"palworld")
application_group="games"
application_icon_path="cacti.svg"
;;
*)
application_group="default"
application_icon_path="${application}.svg"
;;
esac
# create application
echo "Provisioning provider: $application_name"
curl -s -L "${api_base}/core/applications/" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "$auth_header" \
-d "{
\"name\": \"${application_name}\",
\"slug\": \"${application_slug}\",
\"provider\": ${application_provider_pk},
\"backchannel_providers\": [
0
],
\"open_in_new_tab\": ${application_new_tab},
\"meta_launch_url\": \"\",
\"meta_description\": \"\",
\"meta_publisher\": \"\",
\"policy_engine_mode\": \"all\",
\"group\": \"${application_group}\"
}" | jq .
# set icon for application
# when setting icon url, begin path from /media/public
# i'm opting not to use application-icons in the path
curl -L "${api_base}/core/applications/${application_slug}/set_icon_url/" \
-H 'Content-Type: application/json' \
-H "$auth_header" \
-d "{ \"url\": \"/${application_icon_path}\"}"
# # set icon for source
# # when setting icon url, begin path from /media/public
# # i'm opting not to use source-icons in the path
# curl -L "${api_base}/sources/all/${source_slug}/set_icon_url/" \
# -H 'Content-Type: application/json' \
# -H "$auth_header" \
# -d "{ \"url\": \"/${source_icon_path}\"}"
done
# ###### MISC Troubleshooting calls ######
# # list applications
# curl -L "${api_base}/core/applications/" \
# -H 'Accept: application/json' \
# -H "$auth_header" | jq .
# # list providers
# curl -L "${api_base}/providers/proxy/" \
# -H 'Accept: application/json' \
# -H "$auth_header"
# # search flows
# curl -L "${api_base}/flows/instances/?search=implicit" \
# -H 'Accept: application/json' \
# -H "$auth_header" | jq .
# # capture list of property mappings
# curl -L "${api_base}/propertymappings/all/?name=${array_of_property_mappings}" \
# -H 'Accept: application/json' \
# -H "$auth_header" | jq .pk
# # "48cff5e8-e785-4446-a6b1-0e0b3c08951f",
# # "50d4c363-2c6d-4198-851c-cf8325fe9b38",
# # "74cc6522-a0c9-43ea-8151-cfeaba71da18",
# # "6dd3cbd6-4fce-4f7c-8023-7d1290fd5a25",
# # "ab07b851-2c57-4aea-81b6-176dae1cdc31"
# curl -L "${api_base}/propertymappings/all/ab07b851-2c57-4aea-81b6-176dae1cdc31/" \
# -H 'Accept: application/json' \
# -H "$auth_header" | jq .name
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment