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
This commit is contained in:
parent
30d12dafed
commit
5abc3c87b2
29 changed files with 5088 additions and 3 deletions
204
OIDC_ROLE_MAPPING.md
Normal file
204
OIDC_ROLE_MAPPING.md
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
# 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue