headscale/OIDC_ROLE_MAPPING.md
Ryan Malloy 5abc3c87b2 OIDC groups implementation
- Add Groups field to User struct with JSON storage
- Include GetGroups() and SetGroups() helper methods
- Extract groups from OIDC claims in FromClaim()
- Add database migration 202509161200 for groups column
- Update config-example.yaml with groups scope
- Add comprehensive documentation and testing
2026-05-21 17:55:31 -06:00

8.1 KiB

OIDC Role Mapping Implementation

This document describes the complete OIDC role mapping implementation for Headscale and Headplane, enabling enterprise-grade role-based access control through OIDC group claims.

Overview

The implementation bridges OIDC group membership with Headplane role assignments, automatically mapping users to appropriate roles based on their group membership in the identity provider (e.g., Keycloak, Azure AD, Okta).

Architecture

1. Headscale Changes

Database Schema Updates

  • New field: groups column added to users table
  • Type: TEXT field storing JSON array of group names
  • Migration: 202509161200 adds the column with proper rollback support

OIDC Integration Enhancement

  • Groups extraction: Enhanced FromClaim() method to extract and store OIDC groups
  • Claims processing: Groups are extracted from both ID token and UserInfo endpoint
  • Storage: Groups are persisted as JSON in the database on every login

Key Files Modified

  • hscontrol/types/users.go: Added Groups field and helper methods
  • hscontrol/oidc.go: Enhanced claims processing (groups already extracted)
  • hscontrol/db/db.go: Added database migration

Helper Methods Added

// GetGroups returns user's groups as a slice of strings
func (u *User) GetGroups() []string

// SetGroups stores user's groups as JSON in database  
func (u *User) SetGroups(groups []string)

2. Headplane Changes

Role Mapping System

  • Function: mapOidcGroupsToRole() maps OIDC groups to Headplane roles
  • Hierarchy: Roles are assigned based on highest privilege group membership
  • Configurable: Support for custom group-to-role mappings

Database Schema Updates

  • New field: groups column added to users table (JSON array)
  • Migration: 0003_add_groups_column.sql

Authentication Flow Enhancement

  • Groups extraction: Enhanced FlowUser interface to include groups
  • Role assignment: Automatic role assignment based on group mapping during login
  • Persistence: Groups and capabilities are updated on every login

Key Files Modified

  • app/utils/oidc.ts: Enhanced FlowUser interface and group extraction
  • app/server/web/roles.ts: Added group-to-role mapping function
  • app/routes/auth/oidc-callback.ts: Updated authentication flow
  • app/server/db/schema.ts: Added groups field to schema

Default Group Mappings

Group Pattern Headplane Role Capabilities
owner, headplane-owner owner Full system access
admin*, headplane-admin admin Administrative access
network*, headplane-network network_admin Network configuration
it*, headplane-it it_admin IT operations
audit*, headplane-audit auditor Read-only access
Other groups member No UI access (zero capabilities)

Configuration

Headscale OIDC Configuration

oidc:
  issuer: "https://your-provider.com/realm"
  client_id: "headscale-client"
  client_secret: "your-secret"
  scope: ["openid", "profile", "email", "groups"]
  # Groups will be automatically extracted and stored

Headplane OIDC Configuration

oidc:
  enabled: true
  issuer_url: "https://your-provider.com/realm"
  client_id: "headplane-client"
  client_secret: "headplane-secret"
  scope: "openid profile email groups"
  
# Optional: Custom group-to-role mappings
role_mapping:
  owner: ["company-owners", "headplane-owners"]
  admin: ["company-admins", "it-admins"]
  network_admin: ["network-team"]
  auditor: ["audit-team", "compliance"]

Testing Setup

Docker Compose Environment

The implementation includes a complete testing environment with:

  • Keycloak: OIDC provider with preconfigured realm and test users
  • PostgreSQL: Database for Keycloak
  • Headscale: Built with OIDC groups support
  • Headplane: Configured for role mapping

Test Users

Email Password Groups Expected Role
owner@example.com password123 headscale-owner owner
admin@example.com password123 headscale-admin admin
network@example.com password123 headscale-network network_admin
auditor@example.com password123 headscale-audit auditor
member@example.com password123 headscale-member member

Running Tests

cd docker-dev
docker compose -f docker-compose-oidc-test.yml up -d
./test-oidc-roles.sh

Manual Testing

  1. Keycloak Admin: http://localhost:8280 (admin/admin)
  2. Headplane UI: http://localhost:3000/admin
  3. Test login with different users to verify role assignments

Security Considerations

Capabilities Model

  • Zero-trust: New users get member role with zero capabilities by default
  • Explicit mapping: Only users with recognized groups get elevated privileges
  • Dynamic updates: User roles are updated on every login based on current group membership

Group Validation

  • Sanitization: Groups are filtered to ensure they are strings
  • Multiple sources: Groups extracted from both ID token and UserInfo endpoint
  • Fallback handling: Graceful handling when no groups are provided

API Key Management

  • Per-user keys: Each OIDC user should have individual API keys (future enhancement)
  • Shared key limitation: Current implementation uses shared API key for simplicity

Migration Path

Existing Deployments

  1. Backup databases: Both Headscale and Headplane databases
  2. Deploy Headscale changes: Run migration 202509161200
  3. Deploy Headplane changes: Run migration 0003_add_groups_column.sql
  4. Update configurations: Add OIDC group scope and role mappings
  5. Test with non-privileged user: Verify role mapping works correctly

Rollback Procedure

  1. Headscale: Rollback migration removes groups column
  2. Headplane: Remove groups column and revert OIDC callback logic
  3. Configuration: Remove groups from OIDC scope

Benefits

Enterprise Integration

  • SSO Compatibility: Works with any OIDC-compliant provider
  • Group Synchronization: Automatic role updates based on identity provider changes
  • Centralized Management: User access controlled through existing identity systems

Operational Advantages

  • Reduced Manual Work: No manual role assignment required
  • Dynamic Access: User permissions update automatically on login
  • Audit Trail: Clear mapping between identity provider groups and system roles

Security Improvements

  • Principle of Least Privilege: Users get minimum required access
  • Consistent Enforcement: Role-based access control across all features
  • Identity Provider Integration: Leverage existing security policies

Future Enhancements

Planned Improvements

  1. Individual API Keys: Per-user API key generation and management
  2. Group Hierarchies: Support for nested group inheritance
  3. Custom Capabilities: Fine-grained permission customization per group
  4. Audit Logging: Enhanced logging of role assignments and changes
  5. UI Role Management: Administrative interface for role mapping configuration

Integration Opportunities

  1. SCIM Support: Automatic user provisioning and deprovisioning
  2. Just-in-Time Access: Temporary role elevation based on approval workflows
  3. External Authorization: Integration with external policy engines (OPA, etc.)

Troubleshooting

Common Issues

  1. Missing Groups: Ensure OIDC provider includes groups in claims
  2. Wrong Roles: Check group name matching and mapping configuration
  3. No Access: Verify user has at least one recognized group
  4. Token Issues: Check OIDC scope includes "groups"

Debug Steps

  1. Check Headscale logs: Verify groups are being extracted from OIDC claims
  2. Inspect database: Verify groups are stored in users table
  3. Review Headplane logs: Check role mapping function execution
  4. Test OIDC flow: Use OIDC debugging tools to inspect token claims

This implementation provides a robust foundation for enterprise OIDC integration while maintaining security and operational efficiency.