Headscale
This guide explains how to set up single sign-on (SSO) authentication between SmartLink and Headscale using OpenID Connect. Headscale is an open-source implementation of the Tailscale control server.
Prerequisites
- Headscale version 0.22.0 or higher
- Administrative access to the Headscale server
- Application configured in SmartLink with OpenID Connect
- Valid SSL certificate for Headscale (required for OIDC)
Overview
Headscale supports authentication via OpenID Connect, allowing users to authenticate with SmartLink instead of pre-generating authentication keys.
Configuration in SmartLink
1. Create the application
- Log in to SmartLink as an administrator
- Go to Applications → Add
- Create a new application:
- Name: Headscale
- URL:
https://headscale.example.com - Description: Headscale control server
- Icon: Use the Tailscale icon or a custom icon
2. Configure OpenID Connect
- In the Authentication tab
- Select OpenID Connect as the authentication type
- Note the following information:
- Client ID:
headscale-xxxxxx - Client Secret:
secret-xxxxxx - Issuer URL:
https://your-smartlink.link.vaultys.org/api/oidc/[appid] - App ID:
[appid](unique application identifier in SmartLink)
- Client ID:
3. Configure Redirect URLs
Add the callback URL to Allowed Redirect URLs:
https://headscale.example.com/oidc/callback
Note: The
[appid]will be automatically generated when creating the application in SmartLink.
4. Configure Scopes
Required scopes:
openidprofileemailgroups(optional, for group-based ACLs)
Headscale Configuration
1. Configuration in the config.yaml file
Edit the Headscale configuration file (usually /etc/headscale/config.yaml):
# Server configuration
server_url: https://headscale.example.com
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 0.0.0.0:9090
# Database configuration
db_type: sqlite3
db_path: /var/lib/headscale/db.sqlite
# OIDC configuration
oidc:
# Enable OIDC
enabled: true
# SmartLink issuer URL
issuer: "https://your-smartlink.link.vaultys.org/api/oidc/[appid]"
# Client ID and Secret from SmartLink
client_id: "headscale-xxxxxx"
client_secret: "secret-xxxxxx"
# Scopes to request
scope:
- openid
- profile
- email
- groups
# Claims to map user information
claims:
# Claim for email (user identifier)
email: email
# Claim for display name
name: name
# Claim for groups (optional)
groups: groups
# Allowed email domains
allowed_domains:
- example.com
# Allowed groups (optional)
allowed_groups:
- admins
- developers
- users
# Automatically create users
auto_create_users: true
# Authentication token expiry
expiry: 180d
# Use OIDC token expiry
use_expiry_from_token: false
# Namespace/users configuration
ip_prefixes:
- 100.64.0.0/10
- fd7a:115c:a1e0::/48
# DNS configuration
dns_config:
nameservers:
- 1.1.1.1
- 8.8.8.8
domains: []
magic_dns: true
base_domain: example.com
2. Configuration with Environment Variables
Alternatively, you can use environment variables:
export HEADSCALE_OIDC_ENABLED=true
export HEADSCALE_OIDC_ISSUER="https://your-smartlink.link.vaultys.org"
export HEADSCALE_OIDC_CLIENT_ID="headscale-xxxxxx"
export HEADSCALE_OIDC_CLIENT_SECRET="secret-xxxxxx"
export HEADSCALE_OIDC_ALLOWED_DOMAINS="example.com"
3. Starting with Docker
If you are using Docker, here is an example of docker-compose.yml:
version: '3.8'
services:
headscale:
image: headscale/headscale:latest
container_name: headscale
volumes:
- ./config:/etc/headscale
- ./data:/var/lib/headscale
ports:
- "8080:8080"
- "9090:9090"
environment:
- HEADSCALE_OIDC_ENABLED=true
- HEADSCALE_OIDC_ISSUER=https://your-smartlink.link.vaultys.org
- HEADSCALE_OIDC_CLIENT_ID=headscale-xxxxxx
- HEADSCALE_OIDC_CLIENT_SECRET=secret-xxxxxx
command: headscale serve
restart: unless-stopped
4. Reverse Proxy Configuration (Nginx)
Nginx configuration example for Headscale with SSL:
server {
listen 443 ssl http2;
server_name headscale.example.com;
ssl_certificate /etc/letsencrypt/live/headscale.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/headscale.example.com/privkey.pem;
location / {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect http:// https://;
proxy_buffering off;
}
}
Group-Based ACL Configuration
1. Structure of Group-Based ACLs
Create an acls.yaml or acls.hujson file:
groups:
group:admins:
- user1@example.com
group:developers:
- user2@example.com
acls:
- action: accept
src:
- group:admins
dst:
- "*:*"
- action: accept
src:
- group:developers
dst:
- tag:dev:*
- tag:staging:*
tagOwners:
tag:prod:
- group:admins
tag:dev:
- group:developers
tag:staging:
- group:developers
2. Applying ACLs
# Apply ACLs
headscale acls set acls.yaml
# Check ACLs
headscale acls show
Client Configuration
1. Tailscale Client Installation
# Linux
curl -fsSL https://tailscale.com/install.sh | sh
# macOS
brew install tailscale
# Windows
# Download from https://tailscale.com/download
2. Connecting with OIDC
# Connect to the Headscale server with OIDC
tailscale up --login-server https://headscale.example.com
# The browser will open automatically for authentication
# You will be redirected to SmartLink to log in
3. Checking the Connection
# Check the status
tailscale status
# View connected peers
tailscale status --peers
# Check the assigned IP
tailscale ip
Configuration Testing
1. Testing OIDC Authentication
- On a client, run:
tailscale up --login-server https://headscale.example.com - The browser opens to the authentication page
- Click on "Login with OIDC"
- You will be redirected to SmartLink
- Log in with your SmartLink credentials
- After authentication, you will be redirected to Headscale
- The Tailscale client should connect automatically
2. Verification in Headscale
# List users
headscale users list
# List machines
headscale machines list
# View details of a user
headscale users show user@example.com
# View routes
headscale routes list
3. Connectivity Testing
# Ping another machine
ping 100.64.0.2
# Test with MagicDNS hostname
ping machine-name.example.com.beta.tailscale.net
Troubleshooting
Error "OIDC authentication failed"
Issue: OIDC authentication fails
Solution:
- Check the Headscale logs:
journalctl -u headscale -f - Verify the issuer URL in the config
- Test OIDC discovery with your appid:
curl https://your-smartlink.link.vaultys.org/api/oidc/[appid]/.well-known/openid-configuration
Error "Invalid client credentials"
Issue: OIDC client credentials are invalid
Solution:
- Verify the Client ID and Secret in SmartLink
- Ensure there are no spaces or hidden characters
- Restart Headscale after modifying the config
User is created but cannot connect
Issue: User exists in Headscale but connection fails
Solution:
- Check that the email domain is in
allowed_domains - If using
allowed_groups, verify the user's groups - Check ACLs if configured
- Try re-authenticating the user:
headscale preauthkeys create --user user@example.com
Groups are not synchronized
Issue: SmartLink groups are not visible in Headscale
Solution:
- Ensure the
groupsscope is enabled in SmartLink - Test the UserInfo endpoint:
curl -H "Authorization: Bearer TOKEN" https://your-smartlink.link.vaultys.org/api/oidc/[appid]/userinfo - Check the group claims in the Headscale config
- Groups should be an array of strings
SSL/TLS Error
Issue: Certificate error during OIDC connection
Solution:
- Headscale requires HTTPS with a valid certificate
- Do not use self-signed certificates
- Check the certificate chain:
openssl s_client -connect headscale.example.com:443 -showcerts
Security
Recommendations
- Mandatory HTTPS: Headscale requires HTTPS for OIDC
- Firewall: Limit access to the Headscale port (8080)
- Database: Use PostgreSQL in production
- Secrets: Securely store secrets
- Monitoring: Monitor logs and metrics
Key Rotation
# Generate a new node key
headscale nodes key rotate --id NODE_ID
# Revoke a machine
headscale machines delete --id MACHINE_ID
Backup
# Backup the SQLite database
sqlite3 /var/lib/headscale/db.sqlite ".backup /backup/headscale.db"
# Backup the configuration
cp -r /etc/headscale /backup/headscale-config
# With PostgreSQL
pg_dump headscale > /backup/headscale.sql
Advanced Configuration
Multi-User Mode with Namespaces
# In config.yaml
oidc:
# Create a namespace per user
create_namespace_for_user: true
# Namespace format
namespace_format: "{email}"
# Or use a specific claim
namespace_claim: "department"
Integration with Kubernetes
Deploying Headscale in Kubernetes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: headscale
spec:
replicas: 1
selector:
matchLabels:
app: headscale
template:
metadata:
labels:
app: headscale
spec:
containers:
- name: headscale
image: headscale/headscale:latest
ports:
- containerPort: 8080
env:
- name: HEADSCALE_OIDC_ENABLED
value: "true"
- name: HEADSCALE_OIDC_ISSUER
value: "https://your-smartlink.example.com"
- name: HEADSCALE_OIDC_CLIENT_ID
valueFrom:
secretKeyRef:
name: headscale-oidc
key: client-id
- name: HEADSCALE_OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: headscale-oidc
key: client-secret
volumeMounts:
- name: config
mountPath: /etc/headscale
- name: data
mountPath: /var/lib/headscale
volumes:
- name: config
configMap:
name: headscale-config
- name: data
persistentVolumeClaim:
claimName: headscale-data
Monitoring with Prometheus
Headscale exposes metrics on port 9090:
# prometheus.yml
scrape_configs:
- job_name: 'headscale'
static_configs:
- targets: ['headscale.example.com:9090']