mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-31 08:01:34 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			175 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			175 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # 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:
 | ||
| 
 | ||
| ```yaml
 | ||
| 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
 | ||
| 
 | ||
|   # If `strip_email_domain` is set to `true`, the domain part of the username email address will be removed.
 | ||
|   # This will transform `first-name.last-name@example.com` to the user `first-name.last-name`
 | ||
|   # If `strip_email_domain` is set to `false` the domain part will NOT be removed resulting to the following
 | ||
|   # user: `first-name.last-name.example.com`
 | ||
|   strip_email_domain: true
 | ||
| ```
 | ||
| 
 | ||
| ## 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:
 | ||
| 
 | ||
| ```hcl
 | ||
| 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`:
 | ||
| 
 | ||
| ```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](https://console.cloud.google.com) account.
 | ||
| 
 | ||
| Google OAuth has a [verification process](https://support.google.com/cloud/answer/9110914?hl=en) 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
 | ||
| 
 | ||
| 1. Go to [Google Console](https://console.cloud.google.com) and login or create an account if you don't have one.
 | ||
| 2. Create a project (if you don't already have one).
 | ||
| 3. On the left hand menu, go to `APIs and services` -> `Credentials`
 | ||
| 4. Click `Create Credentials` -> `OAuth client ID`
 | ||
| 5. Under `Application Type`, choose `Web Application`
 | ||
| 6. For `Name`, enter whatever you like
 | ||
| 7. Under `Authorised redirect URIs`, use `https://example.com/oidc/callback`, replacing example.com with your Headscale URL.
 | ||
| 8. Click `Save` at the bottom of the form
 | ||
| 9. Take note of the `Client ID` and `Client secret`, you can also download it for reference if you need it.
 | ||
| 10. Edit your headscale config, under `oidc`, filling in your `client_id` and `client_secret`:
 | ||
| 
 | ||
| ```yaml
 | ||
| oidc:
 | ||
|   issuer: "https://accounts.google.com"
 | ||
|   client_id: ""
 | ||
|   client_secret: ""
 | ||
|   scope: ["openid", "profile", "email"]
 | ||
| ```
 | ||
| 
 | ||
| You can also use `allowed_domains` and `allowed_users` to restrict the users who can authenticate.
 |