mirror of
https://github.com/juanfont/headscale.git
synced 2025-08-10 13:46:46 +02:00
Merge branch 'main' into config
This commit is contained in:
commit
0edc0b8689
@ -1,4 +1,5 @@
|
|||||||
.github/workflows/test-integration-v2*
|
.github/workflows/test-integration-v2*
|
||||||
docs/about/features.md
|
docs/about/features.md
|
||||||
docs/ref/configuration.md
|
docs/ref/configuration.md
|
||||||
|
docs/ref/oidc.md
|
||||||
docs/ref/remote-cli.md
|
docs/ref/remote-cli.md
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
[#2614](https://github.com/juanfont/headscale/pull/2614)
|
[#2614](https://github.com/juanfont/headscale/pull/2614)
|
||||||
- Support client verify for DERP
|
- Support client verify for DERP
|
||||||
[#2046](https://github.com/juanfont/headscale/pull/2046)
|
[#2046](https://github.com/juanfont/headscale/pull/2046)
|
||||||
|
- Refactor OpenID Connect documentation
|
||||||
|
[#2625](https://github.com/juanfont/headscale/pull/2625)
|
||||||
- Don't crash if config file is missing
|
- Don't crash if config file is missing
|
||||||
[#2656](https://github.com/juanfont/headscale/pull/2656)
|
[#2656](https://github.com/juanfont/headscale/pull/2656)
|
||||||
|
|
||||||
|
@ -322,51 +322,60 @@ dns:
|
|||||||
# Note: for production you will want to set this to something like:
|
# Note: for production you will want to set this to something like:
|
||||||
unix_socket: /var/run/headscale/headscale.sock
|
unix_socket: /var/run/headscale/headscale.sock
|
||||||
unix_socket_permission: "0770"
|
unix_socket_permission: "0770"
|
||||||
#
|
|
||||||
# headscale supports experimental OpenID connect support,
|
|
||||||
# it is still being tested and might have some bugs, please
|
|
||||||
# help us test it.
|
|
||||||
# OpenID Connect
|
# OpenID Connect
|
||||||
# oidc:
|
# oidc:
|
||||||
|
# # Block startup until the identity provider is available and healthy.
|
||||||
# only_start_if_oidc_is_available: true
|
# only_start_if_oidc_is_available: true
|
||||||
|
#
|
||||||
|
# # OpenID Connect Issuer URL from the identity provider
|
||||||
# issuer: "https://your-oidc.issuer.com/path"
|
# issuer: "https://your-oidc.issuer.com/path"
|
||||||
|
#
|
||||||
|
# # Client ID from the identity provider
|
||||||
# client_id: "your-oidc-client-id"
|
# client_id: "your-oidc-client-id"
|
||||||
|
#
|
||||||
|
# # Client secret generated by the identity provider
|
||||||
|
# # Note: client_secret and client_secret_path are mutually exclusive.
|
||||||
# client_secret: "your-oidc-client-secret"
|
# client_secret: "your-oidc-client-secret"
|
||||||
# # Alternatively, set `client_secret_path` to read the secret from the file.
|
# # Alternatively, set `client_secret_path` to read the secret from the file.
|
||||||
# # It resolves environment variables, making integration to systemd's
|
# # It resolves environment variables, making integration to systemd's
|
||||||
# # `LoadCredential` straightforward:
|
# # `LoadCredential` straightforward:
|
||||||
# client_secret_path: "${CREDENTIALS_DIRECTORY}/oidc_client_secret"
|
# client_secret_path: "${CREDENTIALS_DIRECTORY}/oidc_client_secret"
|
||||||
# # client_secret and client_secret_path are mutually exclusive.
|
|
||||||
#
|
#
|
||||||
# # The amount of time from a node is authenticated with OpenID until it
|
# # The amount of time a node is authenticated with OpenID until it expires
|
||||||
# # expires and needs to reauthenticate.
|
# # and needs to reauthenticate.
|
||||||
# # Setting the value to "0" will mean no expiry.
|
# # Setting the value to "0" will mean no expiry.
|
||||||
# expiry: 180d
|
# expiry: 180d
|
||||||
#
|
#
|
||||||
# # Use the expiry from the token received from OpenID when the user logged
|
# # Use the expiry from the token received from OpenID when the user logged
|
||||||
# # in, this will typically lead to frequent need to reauthenticate and should
|
# # in. This will typically lead to frequent need to reauthenticate and should
|
||||||
# # only been enabled if you know what you are doing.
|
# # only be enabled if you know what you are doing.
|
||||||
# # Note: enabling this will cause `oidc.expiry` to be ignored.
|
# # Note: enabling this will cause `oidc.expiry` to be ignored.
|
||||||
# use_expiry_from_token: false
|
# use_expiry_from_token: false
|
||||||
#
|
#
|
||||||
# # Customize the scopes used in the OIDC flow, defaults to "openid", "profile" and "email" and add custom query
|
# # The OIDC scopes to use, defaults to "openid", "profile" and "email".
|
||||||
# # parameters to the Authorize Endpoint request. Scopes default to "openid", "profile" and "email".
|
# # Custom scopes can be configured as needed, be sure to always include the
|
||||||
|
# # required "openid" scope.
|
||||||
|
# scope: ["openid", "profile", "email"]
|
||||||
#
|
#
|
||||||
# scope: ["openid", "profile", "email", "custom"]
|
# # Provide custom key/value pairs which get sent to the identity provider's
|
||||||
|
# # authorization endpoint.
|
||||||
# extra_params:
|
# extra_params:
|
||||||
# domain_hint: example.com
|
# domain_hint: example.com
|
||||||
#
|
#
|
||||||
# # List allowed principal domains and/or users. If an authenticated user's domain is not in this list, the
|
# # Only accept users whose email domain is part of the allowed_domains list.
|
||||||
# # authentication request will be rejected.
|
|
||||||
#
|
|
||||||
# allowed_domains:
|
# allowed_domains:
|
||||||
# - example.com
|
# - example.com
|
||||||
# # Note: Groups from keycloak have a leading '/'
|
#
|
||||||
# allowed_groups:
|
# # Only accept users whose email address is part of the allowed_users list.
|
||||||
# - /headscale
|
|
||||||
# allowed_users:
|
# allowed_users:
|
||||||
# - alice@example.com
|
# - alice@example.com
|
||||||
#
|
#
|
||||||
|
# # Only accept users which are members of at least one group in the
|
||||||
|
# # allowed_groups list.
|
||||||
|
# allowed_groups:
|
||||||
|
# - /headscale
|
||||||
|
#
|
||||||
# # Optional: PKCE (Proof Key for Code Exchange) configuration
|
# # Optional: PKCE (Proof Key for Code Exchange) configuration
|
||||||
# # PKCE adds an additional layer of security to the OAuth 2.0 authorization code flow
|
# # PKCE adds an additional layer of security to the OAuth 2.0 authorization code flow
|
||||||
# # by preventing authorization code interception attacks
|
# # by preventing authorization code interception attacks
|
||||||
@ -374,6 +383,7 @@ unix_socket_permission: "0770"
|
|||||||
# pkce:
|
# pkce:
|
||||||
# # Enable or disable PKCE support (default: false)
|
# # Enable or disable PKCE support (default: false)
|
||||||
# enabled: false
|
# enabled: false
|
||||||
|
#
|
||||||
# # PKCE method to use:
|
# # PKCE method to use:
|
||||||
# # - plain: Use plain code verifier
|
# # - plain: Use plain code verifier
|
||||||
# # - S256: Use SHA256 hashed code verifier (default, recommended)
|
# # - S256: Use SHA256 hashed code verifier (default, recommended)
|
||||||
|
@ -28,10 +28,9 @@ provides on overview of Headscale's feature and compatibility with the Tailscale
|
|||||||
routers](../ref/routes.md#automatically-approve-routes-of-a-subnet-router) and [exit
|
routers](../ref/routes.md#automatically-approve-routes-of-a-subnet-router) and [exit
|
||||||
nodes](../ref/routes.md#automatically-approve-an-exit-node-with-auto-approvers)
|
nodes](../ref/routes.md#automatically-approve-an-exit-node-with-auto-approvers)
|
||||||
- [x] [Tailscale SSH](https://tailscale.com/kb/1193/tailscale-ssh)
|
- [x] [Tailscale SSH](https://tailscale.com/kb/1193/tailscale-ssh)
|
||||||
* [ ] Node registration using Single-Sign-On (OpenID Connect) ([GitHub label "OIDC"](https://github.com/juanfont/headscale/labels/OIDC))
|
* [x] [Node registration using Single-Sign-On (OpenID Connect)](../ref/oidc.md) ([GitHub label "OIDC"](https://github.com/juanfont/headscale/labels/OIDC))
|
||||||
- [x] Basic registration
|
- [x] Basic registration
|
||||||
- [x] Update user profile from identity provider
|
- [x] Update user profile from identity provider
|
||||||
- [ ] Dynamic ACL support
|
|
||||||
- [ ] OIDC groups cannot be used in ACLs
|
- [ ] OIDC groups cannot be used in ACLs
|
||||||
- [ ] [Funnel](https://tailscale.com/kb/1223/funnel) ([#1040](https://github.com/juanfont/headscale/issues/1040))
|
- [ ] [Funnel](https://tailscale.com/kb/1223/funnel) ([#1040](https://github.com/juanfont/headscale/issues/1040))
|
||||||
- [ ] [Serve](https://tailscale.com/kb/1312/serve) ([#1234](https://github.com/juanfont/headscale/issues/1921))
|
- [ ] [Serve](https://tailscale.com/kb/1312/serve) ([#1234](https://github.com/juanfont/headscale/issues/1921))
|
||||||
|
448
docs/ref/oidc.md
448
docs/ref/oidc.md
@ -1,162 +1,272 @@
|
|||||||
# Configuring headscale to use OIDC authentication
|
# OpenID Connect
|
||||||
|
|
||||||
In order to authenticate users through a centralized solution one must enable the OIDC integration.
|
Headscale supports authentication via external identity providers using OpenID Connect (OIDC). It features:
|
||||||
|
|
||||||
Known limitations:
|
- Autoconfiguration via OpenID Connect Discovery Protocol
|
||||||
|
- [Proof Key for Code Exchange (PKCE) code verification](#enable-pkce-recommended)
|
||||||
|
- [Authorization based on a user's domain, email address or group membership](#authorize-users-with-filters)
|
||||||
|
- Synchronization of [standard OIDC claims](#supported-oidc-claims)
|
||||||
|
|
||||||
- No dynamic ACL support
|
Please see [limitations](#limitations) for known issues and limitations.
|
||||||
- OIDC groups cannot be used in ACLs
|
|
||||||
|
|
||||||
## Basic configuration
|
## Configuration
|
||||||
|
|
||||||
In your `config.yaml`, customize this to your liking:
|
OpenID requires configuration in Headscale and your identity provider:
|
||||||
|
|
||||||
```yaml title="config.yaml"
|
- Headscale: The `oidc` section of the Headscale [configuration](configuration.md) contains all available configuration
|
||||||
oidc:
|
options along with a description and their default values.
|
||||||
# Block further startup until the OIDC provider is healthy and available
|
- Identity provider: Please refer to the official documentation of your identity provider for specific instructions.
|
||||||
only_start_if_oidc_is_available: true
|
Additionally, there might be some useful hints in the [Identity provider specific
|
||||||
# Specified by your OIDC provider
|
configuration](#identity-provider-specific-configuration) section below.
|
||||||
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
|
### Basic configuration
|
||||||
# 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,
|
A basic configuration connects Headscale to an identity provider and typically requires:
|
||||||
# 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
|
- OpenID Connect Issuer URL from the identity provider. Headscale uses the OpenID Connect Discovery Protocol 1.0 to
|
||||||
# PKCE adds an additional layer of security to the OAuth 2.0 authorization code flow
|
automatically obtain OpenID configuration parameters (example: `https://sso.example.com`).
|
||||||
# by preventing authorization code interception attacks
|
- Client ID from the identity provider (example: `headscale`).
|
||||||
# See https://datatracker.ietf.org/doc/html/rfc7636
|
- Client secret generated by the identity provider (example: `generated-secret`).
|
||||||
pkce:
|
- Redirect URI for your identity provider (example: `https://headscale.example.com/oidc/callback`).
|
||||||
# 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
|
=== "Headscale"
|
||||||
|
|
||||||
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:
|
```yaml
|
||||||
|
oidc:
|
||||||
|
issuer: "https://sso.example.com"
|
||||||
|
client_id: "headscale"
|
||||||
|
client_secret: "generated-secret"
|
||||||
|
```
|
||||||
|
|
||||||
```hcl title="terraform.hcl"
|
=== "Identity provider"
|
||||||
resource "azuread_application" "headscale" {
|
|
||||||
display_name = "Headscale"
|
|
||||||
|
|
||||||
sign_in_audience = "AzureADMyOrg"
|
* Create a new confidential client (`Client ID`, `Client secret`)
|
||||||
fallback_public_client_enabled = false
|
* Add Headscale's OIDC callback URL as valid redirect URL: `https://headscale.example.com/oidc/callback`
|
||||||
|
* Configure additional parameters to improve user experience such as: name, description, logo, …
|
||||||
|
|
||||||
required_resource_access {
|
### Enable PKCE (recommended)
|
||||||
// Microsoft Graph
|
|
||||||
resource_app_id = "00000003-0000-0000-c000-000000000000"
|
|
||||||
|
|
||||||
resource_access {
|
Proof Key for Code Exchange (PKCE) adds an additional layer of security to the OAuth 2.0 authorization code flow by
|
||||||
// scope: profile
|
preventing authorization code interception attacks, see: <https://datatracker.ietf.org/doc/html/rfc7636>. PKCE is
|
||||||
id = "14dad69e-099b-42c9-810b-d002981feec1"
|
recommended and needs to be configured for Headscale and the identity provider alike:
|
||||||
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 {
|
=== "Headscale"
|
||||||
access_token_issuance_enabled = false
|
|
||||||
id_token_issuance_enabled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
group_membership_claims = ["SecurityGroup"]
|
```yaml hl_lines="5-6"
|
||||||
optional_claims {
|
oidc:
|
||||||
# Expose group memberships
|
issuer: "https://sso.example.com"
|
||||||
id_token {
|
client_id: "headscale"
|
||||||
name = "groups"
|
client_secret: "generated-secret"
|
||||||
}
|
pkce:
|
||||||
}
|
enabled: true
|
||||||
}
|
```
|
||||||
|
|
||||||
resource "azuread_application_password" "headscale-application-secret" {
|
=== "Identity provider"
|
||||||
display_name = "Headscale Server"
|
|
||||||
application_object_id = azuread_application.headscale.object_id
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "azuread_service_principal" "headscale" {
|
* Enable PKCE for the headscale client
|
||||||
application_id = azuread_application.headscale.application_id
|
* Set the PKCE challenge method to "S256"
|
||||||
}
|
|
||||||
|
|
||||||
resource "azuread_service_principal_password" "headscale" {
|
### Authorize users with filters
|
||||||
service_principal_id = azuread_service_principal.headscale.id
|
|
||||||
end_date_relative = "44640h"
|
|
||||||
}
|
|
||||||
|
|
||||||
output "headscale_client_id" {
|
Headscale allows to filter for allowed users based on their domain, email address or group membership. These filters can
|
||||||
value = azuread_application.headscale.application_id
|
be helpful to apply additional restrictions and control which users are allowed to join. Filters are disabled by
|
||||||
}
|
default, users are allowed to join once the authentication with the identity provider succeeds. In case multiple filters
|
||||||
|
are configured, a user needs to pass all of them.
|
||||||
|
|
||||||
output "headscale_client_secret" {
|
=== "Allowed domains"
|
||||||
value = azuread_application_password.headscale-application-secret.value
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And in your headscale `config.yaml`:
|
* Check the email domain of each authenticating user against the list of allowed domains and only authorize users
|
||||||
|
whose email domain matches `example.com`.
|
||||||
|
* Access allowed: `alice@example.com`
|
||||||
|
* Access denied: `bob@example.net`
|
||||||
|
|
||||||
```yaml title="config.yaml"
|
```yaml hl_lines="5-6"
|
||||||
oidc:
|
oidc:
|
||||||
issuer: "https://login.microsoftonline.com/<tenant-UUID>/v2.0"
|
issuer: "https://sso.example.com"
|
||||||
client_id: "<client-id-from-terraform>"
|
client_id: "headscale"
|
||||||
client_secret: "<client-secret-from-terraform>"
|
client_secret: "generated-secret"
|
||||||
|
allowed_domains:
|
||||||
|
- "example.com"
|
||||||
|
```
|
||||||
|
|
||||||
# Optional: add "groups"
|
=== "Allowed users/emails"
|
||||||
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
|
* Check the email address of each authenticating user against the list of allowed email addresses and only authorize
|
||||||
|
users whose email is part of the `allowed_users` list.
|
||||||
|
* Access allowed: `alice@example.com`, `bob@example.net`
|
||||||
|
* Access denied: `mallory@example.net`
|
||||||
|
|
||||||
In order to integrate headscale with Google, you'll need to have a [Google Cloud Console](https://console.cloud.google.com) account.
|
```yaml hl_lines="5-7"
|
||||||
|
oidc:
|
||||||
|
issuer: "https://sso.example.com"
|
||||||
|
client_id: "headscale"
|
||||||
|
client_secret: "generated-secret"
|
||||||
|
allowed_users:
|
||||||
|
- "alice@example.com"
|
||||||
|
- "bob@example.net"
|
||||||
|
```
|
||||||
|
|
||||||
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.
|
=== "Allowed groups"
|
||||||
|
|
||||||
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.
|
* Use the OIDC `groups` claim of each authenticating user to get their group membership and only authorize users
|
||||||
|
which are members in at least one of the referenced groups.
|
||||||
|
* Access allowed: users in the `headscale_users` group
|
||||||
|
* Access denied: users without groups, users with other groups
|
||||||
|
|
||||||
### Steps
|
```yaml hl_lines="5-7"
|
||||||
|
oidc:
|
||||||
|
issuer: "https://sso.example.com"
|
||||||
|
client_id: "headscale"
|
||||||
|
client_secret: "generated-secret"
|
||||||
|
scope: ["openid", "profile", "email", "groups"]
|
||||||
|
allowed_groups:
|
||||||
|
- "headscale_users"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize node expiration
|
||||||
|
|
||||||
|
The node expiration is the amount of time a node is authenticated with OpenID Connect until it expires and needs to
|
||||||
|
reauthenticate. The default node expiration is 180 days. This can either be customized or set to the expiration from the
|
||||||
|
Access Token.
|
||||||
|
|
||||||
|
=== "Customize node expiration"
|
||||||
|
|
||||||
|
```yaml hl_lines="5"
|
||||||
|
oidc:
|
||||||
|
issuer: "https://sso.example.com"
|
||||||
|
client_id: "headscale"
|
||||||
|
client_secret: "generated-secret"
|
||||||
|
expiry: 30d # Use 0 to disable node expiration
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Use expiration from Access Token"
|
||||||
|
|
||||||
|
Please keep in mind that the Access Token is typically a short-lived token that expires within a few minutes. You
|
||||||
|
will have to configure token expiration in your identity provider to avoid frequent reauthentication.
|
||||||
|
|
||||||
|
|
||||||
|
```yaml hl_lines="5"
|
||||||
|
oidc:
|
||||||
|
issuer: "https://sso.example.com"
|
||||||
|
client_id: "headscale"
|
||||||
|
client_secret: "generated-secret"
|
||||||
|
use_expiry_from_token: true
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! tip "Expire a node and force re-authentication"
|
||||||
|
|
||||||
|
A node can be expired immediately via:
|
||||||
|
```console
|
||||||
|
headscale node expire -i <NODE_ID>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reference a user in the policy
|
||||||
|
|
||||||
|
You may refer to users in the Headscale policy via:
|
||||||
|
|
||||||
|
- Email address
|
||||||
|
- Username
|
||||||
|
- Provider identifier (only available in the database or from your identity provider)
|
||||||
|
|
||||||
|
!!! note "A user identifier in the policy must contain a single `@`"
|
||||||
|
|
||||||
|
The Headscale policy requires a single `@` to reference a user. If the username or provider identifier doesn't
|
||||||
|
already contain a single `@`, it needs to be appended at the end. For example: the username `ssmith` has to be
|
||||||
|
written as `ssmith@` to be correctly identified as user within the policy.
|
||||||
|
|
||||||
|
!!! warning "Email address or username might be updated by users"
|
||||||
|
|
||||||
|
Many identity providers allow users to update their own profile. Depending on the identity provider and its
|
||||||
|
configuration, the values for username or email address might change over time. This might have unexpected
|
||||||
|
consequences for Headscale where a policy might no longer work or a user might obtain more access by hijacking an
|
||||||
|
existing username or email address.
|
||||||
|
|
||||||
|
## Supported OIDC claims
|
||||||
|
|
||||||
|
Headscale uses [the standard OIDC claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) to
|
||||||
|
populate and update its local user profile on each login. OIDC claims are read from the ID Token or from the UserInfo
|
||||||
|
endpoint.
|
||||||
|
|
||||||
|
| Headscale profile | OIDC claim | Notes / examples |
|
||||||
|
| ------------------- | -------------------- | ------------------------------------------------------------------------------------------------- |
|
||||||
|
| email address | `email` | Only used when `email_verified: true` |
|
||||||
|
| display name | `name` | eg: `Sam Smith` |
|
||||||
|
| username | `preferred_username` | Depends on identity provider, eg: `ssmith`, `ssmith@idp.example.com`, `\\example.com\ssmith` |
|
||||||
|
| profile picture | `picture` | URL to a profile picture or avatar |
|
||||||
|
| provider identifier | `iss`, `sub` | A stable and unique identifier for a user, typically a combination of `iss` and `sub` OIDC claims |
|
||||||
|
| | `groups` | [Only used to filter for allowed groups](#authorize-users-with-filters) |
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- Support for OpenID Connect aims to be generic and vendor independent. It offers only limited support for quirks of
|
||||||
|
specific identity providers.
|
||||||
|
- OIDC groups cannot be used in ACLs.
|
||||||
|
- The username provided by the identity provider needs to adhere to this pattern:
|
||||||
|
- The username must be at least two characters long.
|
||||||
|
- It must only contain letters, digits, hyphens, dots, underscores, and up to a single `@`.
|
||||||
|
- The username must start with a letter.
|
||||||
|
- A user's email address is only synchronized to the local user profile when the identity provider marks the email
|
||||||
|
address as verified (`email_verified: true`).
|
||||||
|
|
||||||
|
Please see the [GitHub label "OIDC"](https://github.com/juanfont/headscale/labels/OIDC) for OIDC related issues.
|
||||||
|
|
||||||
|
## Identity provider specific configuration
|
||||||
|
|
||||||
|
!!! warning "Third-party software and services"
|
||||||
|
|
||||||
|
This section of the documentation is specific for third-party software and services. We recommend users read the
|
||||||
|
third-party documentation on how to configure and integrate an OIDC client. Please see the [Configuration
|
||||||
|
section](#configuration) for a description of Headscale's OIDC related configuration settings.
|
||||||
|
|
||||||
|
Any identity provider with OpenID Connect support should "just work" with Headscale. The following identity providers
|
||||||
|
are known to work:
|
||||||
|
|
||||||
|
- [Authelia](#authelia)
|
||||||
|
- [Authentik](#authentik)
|
||||||
|
- [Kanidm](#kanidm)
|
||||||
|
- [Keycloak](#keycloak)
|
||||||
|
|
||||||
|
### Authelia
|
||||||
|
|
||||||
|
Authelia is fully supported by Headscale.
|
||||||
|
|
||||||
|
#### Additional configuration to authorize users based on filters
|
||||||
|
|
||||||
|
Authelia (4.39.0 or newer) no longer provides standard OIDC claims such as `email` or `groups` via the ID Token. The
|
||||||
|
OIDC `email` and `groups` claims are used to [authorize users with filters](#authorize-users-with-filters). This extra
|
||||||
|
configuration step is **only** needed if you need to authorize access based on one of the following user properties:
|
||||||
|
|
||||||
|
- domain
|
||||||
|
- email address
|
||||||
|
- group membership
|
||||||
|
|
||||||
|
Please follow the instructions from Authelia's documentation on how to [Restore Functionality Prior to Claims
|
||||||
|
Parameter](https://www.authelia.com/integration/openid-connect/openid-connect-1.0-claims/#restore-functionality-prior-to-claims-parameter).
|
||||||
|
|
||||||
|
### Authentik
|
||||||
|
|
||||||
|
- Authentik is fully supported by Headscale.
|
||||||
|
- [Headscale does not JSON Web Encryption](https://github.com/juanfont/headscale/issues/2446). Leave the field
|
||||||
|
`Encryption Key` in the providers section unset.
|
||||||
|
|
||||||
|
### Google OAuth
|
||||||
|
|
||||||
|
!!! warning "No username due to missing preferred_username"
|
||||||
|
|
||||||
|
Google OAuth does not send the `preferred_username` claim when the scope `profile` is requested. The username in
|
||||||
|
Headscale will be blank/not set.
|
||||||
|
|
||||||
|
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.
|
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).
|
2. Create a project (if you don't already have one).
|
||||||
@ -164,58 +274,44 @@ However if you don't have a domain, or need to add users outside of your domain,
|
|||||||
4. Click `Create Credentials` -> `OAuth client ID`
|
4. Click `Create Credentials` -> `OAuth client ID`
|
||||||
5. Under `Application Type`, choose `Web Application`
|
5. Under `Application Type`, choose `Web Application`
|
||||||
6. For `Name`, enter whatever you like
|
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.
|
7. Under `Authorised redirect URIs`, add Headscale's OIDC callback URL: `https://headscale.example.com/oidc/callback`
|
||||||
8. Click `Save` at the bottom of the form
|
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.
|
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`:
|
10. [Configure Headscale following the "Basic configuration" steps](#basic-configuration). The issuer URL for Google
|
||||||
```yaml title="config.yaml"
|
OAuth is: `https://accounts.google.com`.
|
||||||
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.
|
### Kanidm
|
||||||
|
|
||||||
## Authelia
|
- Kanidm is fully supported by Headscale.
|
||||||
|
- Groups for the [allowed groups filter](#authorize-users-with-filters) need to be specified with their full SPN, for
|
||||||
|
example: `headscale_users@sso.example.com`.
|
||||||
|
|
||||||
Authelia since v4.39.0, has removed most claims from the `ID Token`, they are still available when application queries [UserInfo Endpoint](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo).
|
### Keycloak
|
||||||
|
|
||||||
Following config restores sending 'default' claims in the `ID Token`
|
Keycloak is fully supported by Headscale.
|
||||||
|
|
||||||
For more information please read: [Authelia restore functionality prior to claims parameter](https://www.authelia.com/integration/openid-connect/openid-connect-1.0-claims/#restore-functionality-prior-to-claims-parameter)
|
#### Additional configuration to use the allowed groups filter
|
||||||
|
|
||||||
```yaml
|
Keycloak has no built-in client scope for the OIDC `groups` claim. This extra configuration step is **only** needed if
|
||||||
identity_providers:
|
you need to [authorize access based on group membership](#authorize-users-with-filters).
|
||||||
oidc:
|
|
||||||
claims_policies:
|
- Create a new client scope `groups` for OpenID Connect:
|
||||||
default:
|
- Configure a `Group Membership` mapper with name `groups` and the token claim name `groups`.
|
||||||
id_token:
|
- Enable the mapper for the ID Token, Access Token and UserInfo endpoint.
|
||||||
[
|
- Configure the new client scope for your Headscale client:
|
||||||
"groups",
|
- Edit the Headscale client.
|
||||||
"email",
|
- Search for the client scope `group`.
|
||||||
"email_verified",
|
- Add it with assigned type `Default`.
|
||||||
"alt_emails",
|
- [Configure the allowed groups in Headscale](#authorize-users-with-filters). Keep in mind that groups in Keycloak start
|
||||||
"preferred_username",
|
with a leading `/`.
|
||||||
"name",
|
|
||||||
]
|
### Microsoft Entra ID
|
||||||
clients:
|
|
||||||
- client_id: "headscale"
|
In order to integrate Headscale with Microsoft Entra ID, you'll need to provision an App Registration with the correct
|
||||||
client_name: "headscale"
|
scopes and redirect URI.
|
||||||
client_secret: ""
|
|
||||||
public: false
|
[Configure Headscale following the "Basic configuration" steps](#basic-configuration). The issuer URL for Microsoft
|
||||||
claims_policy: "default"
|
Entra ID is: `https://login.microsoftonline.com/<tenant-UUID>/v2.0`. The following `extra_params` might be useful:
|
||||||
authorization_policy: "two_factor"
|
|
||||||
require_pkce: true
|
- `domain_hint: example.com` to use your own domain
|
||||||
pkce_challenge_method: "S256"
|
- `prompt: select_account` to force an account picker during login
|
||||||
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"
|
|
||||||
```
|
|
||||||
|
@ -822,7 +822,7 @@ func (h *Headscale) Serve() error {
|
|||||||
case syscall.SIGHUP:
|
case syscall.SIGHUP:
|
||||||
log.Info().
|
log.Info().
|
||||||
Str("signal", sig.String()).
|
Str("signal", sig.String()).
|
||||||
Msg("Received SIGHUP, reloading ACL and Config")
|
Msg("Received SIGHUP, reloading ACL policy")
|
||||||
|
|
||||||
if h.cfg.Policy.IsEmpty() {
|
if h.cfg.Policy.IsEmpty() {
|
||||||
continue
|
continue
|
||||||
|
@ -64,9 +64,8 @@ var errMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed, "method not
|
|||||||
var ErrRegisterMethodCLIDoesNotSupportExpire = errors.New(
|
var ErrRegisterMethodCLIDoesNotSupportExpire = errors.New(
|
||||||
"machines registered with CLI does not support expire",
|
"machines registered with CLI does not support expire",
|
||||||
)
|
)
|
||||||
var ErrNoCapabilityVersion = errors.New("no capability version set")
|
|
||||||
|
|
||||||
func parseCabailityVersion(req *http.Request) (tailcfg.CapabilityVersion, error) {
|
func parseCapabilityVersion(req *http.Request) (tailcfg.CapabilityVersion, error) {
|
||||||
clientCapabilityStr := req.URL.Query().Get("v")
|
clientCapabilityStr := req.URL.Query().Get("v")
|
||||||
|
|
||||||
if clientCapabilityStr == "" {
|
if clientCapabilityStr == "" {
|
||||||
@ -132,7 +131,7 @@ func (h *Headscale) KeyHandler(
|
|||||||
req *http.Request,
|
req *http.Request,
|
||||||
) {
|
) {
|
||||||
// New Tailscale clients send a 'v' parameter to indicate the CurrentCapabilityVersion
|
// New Tailscale clients send a 'v' parameter to indicate the CurrentCapabilityVersion
|
||||||
capVer, err := parseCabailityVersion(req)
|
capVer, err := parseCapabilityVersion(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(writer, err)
|
httpError(writer, err)
|
||||||
return
|
return
|
||||||
|
@ -176,7 +176,7 @@ nav:
|
|||||||
- Windows: usage/connect/windows.md
|
- Windows: usage/connect/windows.md
|
||||||
- Reference:
|
- Reference:
|
||||||
- Configuration: ref/configuration.md
|
- Configuration: ref/configuration.md
|
||||||
- OIDC authentication: ref/oidc.md
|
- OpenID Connect: ref/oidc.md
|
||||||
- Routes: ref/routes.md
|
- Routes: ref/routes.md
|
||||||
- TLS: ref/tls.md
|
- TLS: ref/tls.md
|
||||||
- ACLs: ref/acls.md
|
- ACLs: ref/acls.md
|
||||||
|
Loading…
Reference in New Issue
Block a user