Configuring headscale to use OIDC authentication¶
In order to authenticate users through a centralized solution one must enable the OIDC integration.
Known limitations:
- No dynamic ACL support
- OIDC groups cannot be used in ACLs
Basic configuration¶
In your config.yaml, customize this to your liking:
oidc:
# Block further startup until the OIDC provider is healthy and available
only_start_if_oidc_is_available: true
# Specified by your OIDC provider
issuer: "https://your-oidc.issuer.com/path"
# Specified/generated by your OIDC provider
client_id: "your-oidc-client-id"
client_secret: "your-oidc-client-secret"
# alternatively, set `client_secret_path` to read the secret from the file.
# It resolves environment variables, making integration to systemd's
# `LoadCredential` straightforward:
#client_secret_path: "${CREDENTIALS_DIRECTORY}/oidc_client_secret"
# as third option, it's also possible to load the oidc secret from environment variables
# set HEADSCALE_OIDC_CLIENT_SECRET to the required value
# Customize the scopes used in the OIDC flow, defaults to "openid", "profile" and "email" and add custom query
# parameters to the Authorize Endpoint request. Scopes default to "openid", "profile" and "email".
scope: ["openid", "profile", "email", "custom"]
# Optional: Passed on to the browser login request – used to tweak behaviour for the OIDC provider
extra_params:
domain_hint: example.com
# Optional: List allowed principal domains and/or users. If an authenticated user's domain is not in this list,
# the authentication request will be rejected.
allowed_domains:
- example.com
# Optional. Note that groups from Keycloak have a leading '/'.
allowed_groups:
- /headscale
# Optional.
allowed_users:
- alice@example.com
# Optional: PKCE (Proof Key for Code Exchange) configuration
# PKCE adds an additional layer of security to the OAuth 2.0 authorization code flow
# by preventing authorization code interception attacks
# See https://datatracker.ietf.org/doc/html/rfc7636
pkce:
# Enable or disable PKCE support (default: false)
enabled: false
# PKCE method to use:
# - plain: Use plain code verifier
# - S256: Use SHA256 hashed code verifier (default, recommended)
method: S256
Azure AD example¶
In order to integrate headscale with Azure Active Directory, we'll need to provision an App Registration with the correct scopes and redirect URI. Here with Terraform:
resource "azuread_application" "headscale" {
display_name = "Headscale"
sign_in_audience = "AzureADMyOrg"
fallback_public_client_enabled = false
required_resource_access {
// Microsoft Graph
resource_app_id = "00000003-0000-0000-c000-000000000000"
resource_access {
// scope: profile
id = "14dad69e-099b-42c9-810b-d002981feec1"
type = "Scope"
}
resource_access {
// scope: openid
id = "37f7f235-527c-4136-accd-4a02d197296e"
type = "Scope"
}
resource_access {
// scope: email
id = "64a6cdd6-aab1-4aaf-94b8-3cc8405e90d0"
type = "Scope"
}
}
web {
# Points at your running headscale instance
redirect_uris = ["https://headscale.example.com/oidc/callback"]
implicit_grant {
access_token_issuance_enabled = false
id_token_issuance_enabled = true
}
}
group_membership_claims = ["SecurityGroup"]
optional_claims {
# Expose group memberships
id_token {
name = "groups"
}
}
}
resource "azuread_application_password" "headscale-application-secret" {
display_name = "Headscale Server"
application_object_id = azuread_application.headscale.object_id
}
resource "azuread_service_principal" "headscale" {
application_id = azuread_application.headscale.application_id
}
resource "azuread_service_principal_password" "headscale" {
service_principal_id = azuread_service_principal.headscale.id
end_date_relative = "44640h"
}
output "headscale_client_id" {
value = azuread_application.headscale.application_id
}
output "headscale_client_secret" {
value = azuread_application_password.headscale-application-secret.value
}
And in your headscale config.yaml:
oidc:
issuer: "https://login.microsoftonline.com/<tenant-UUID>/v2.0"
client_id: "<client-id-from-terraform>"
client_secret: "<client-secret-from-terraform>"
# Optional: add "groups"
scope: ["openid", "profile", "email"]
extra_params:
# Use your own domain, associated with Azure AD
domain_hint: example.com
# Optional: Force the Azure AD account picker
prompt: select_account
Google OAuth Example¶
In order to integrate headscale with Google, you'll need to have a Google Cloud Console account.
Google OAuth has a verification process if you need to have users authenticate who are outside of your domain. If you only need to authenticate users from your domain name (ie @example.com), you don't need to go through the verification process.
However if you don't have a domain, or need to add users outside of your domain, you can manually add emails via Google Console.
Steps¶
- Go to Google Console and login or create an account if you don't have one.
- Create a project (if you don't already have one).
- On the left hand menu, go to
APIs and services->Credentials - Click
Create Credentials->OAuth client ID - Under
Application Type, chooseWeb Application - For
Name, enter whatever you like - Under
Authorised redirect URIs, usehttps://example.com/oidc/callback, replacing example.com with your headscale URL. - Click
Saveat the bottom of the form - Take note of the
Client IDandClient secret, you can also download it for reference if you need it. - Edit your headscale config, under
oidc, filling in yourclient_idandclient_secret:
You can also use allowed_domains and allowed_users to restrict the users who can authenticate.
Authelia¶
Authelia since v4.39.0, has removed most claims from the ID Token, they are still available when application queries UserInfo Endpoint.
Following config restores sending 'default' claims in the ID Token
For more information please read: Authelia restore functionality prior to claims parameter
identity_providers:
oidc:
claims_policies:
default:
id_token: ['groups', 'email', 'email_verified', 'alt_emails', 'preferred_username', 'name']
clients:
- client_id: 'headscale'
client_name: 'headscale'
client_secret: ''
public: false
claims_policy: 'default'
authorization_policy: 'two_factor'
require_pkce: true
pkce_challenge_method: 'S256'
redirect_uris:
- 'https://headscale.example.com/oidc/callback'
scopes:
- 'openid'
- 'profile'
- 'groups'
- 'email'
userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'client_secret_basic'