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

204 lines
8.1 KiB
Markdown

# 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
```go
// 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
```yaml
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
```yaml
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
```bash
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.