- 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
309 lines
7.6 KiB
Markdown
309 lines
7.6 KiB
Markdown
# API Reference: OIDC Groups
|
|
|
|
This document describes the API changes related to OIDC group storage and management introduced in Headscale v0.24.0.
|
|
|
|
## Overview
|
|
|
|
Headscale now stores OIDC group membership information extracted from authentication claims. This enables external integrations to implement role-based access control based on a user's group membership.
|
|
|
|
## User API Changes
|
|
|
|
### User Object Schema
|
|
|
|
The User object now includes group information when users authenticate via OIDC:
|
|
|
|
```json
|
|
{
|
|
"id": "1",
|
|
"name": "alice",
|
|
"createdAt": "2024-01-01T00:00:00Z",
|
|
"displayName": "Alice Smith",
|
|
"email": "alice@example.com",
|
|
"providerId": "https://provider.com/alice",
|
|
"provider": "oidc",
|
|
"profilePicUrl": "https://provider.com/avatar/alice.jpg",
|
|
"groups": ["admin", "developers", "security-team"]
|
|
}
|
|
```
|
|
|
|
### Field Descriptions
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `groups` | `string[]` | Array of group names extracted from OIDC claims. Empty array for non-OIDC users. |
|
|
|
|
## API Endpoints
|
|
|
|
### List Users
|
|
|
|
Returns all users including their group membership.
|
|
|
|
**Request:**
|
|
```http
|
|
GET /api/v1/user
|
|
Authorization: Bearer <api-key>
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"users": [
|
|
{
|
|
"id": "1",
|
|
"name": "alice",
|
|
"email": "alice@example.com",
|
|
"provider": "oidc",
|
|
"groups": ["admin", "developers"]
|
|
},
|
|
{
|
|
"id": "2",
|
|
"name": "bob",
|
|
"email": "",
|
|
"provider": "cli",
|
|
"groups": []
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Get User
|
|
|
|
Returns a specific user including group membership.
|
|
|
|
**Request:**
|
|
```http
|
|
GET /api/v1/user/{name}
|
|
Authorization: Bearer <api-key>
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"user": {
|
|
"id": "1",
|
|
"name": "alice",
|
|
"email": "alice@example.com",
|
|
"provider": "oidc",
|
|
"groups": ["admin", "developers", "security-team"]
|
|
}
|
|
}
|
|
```
|
|
|
|
## Group Management
|
|
|
|
### Automatic Group Updates
|
|
|
|
Groups are automatically updated when OIDC users authenticate:
|
|
|
|
1. User logs in via OIDC
|
|
2. Groups are extracted from ID token and/or UserInfo endpoint
|
|
3. User's group membership is updated in the database
|
|
4. API responses include updated group information
|
|
|
|
### Group Sources
|
|
|
|
Groups can be extracted from multiple sources in OIDC claims:
|
|
|
|
- **Standard `groups` claim**: Most common format
|
|
- **`roles` claim**: Alternative role-based claim
|
|
- **Provider-specific claims**: e.g., `cognito:groups` for AWS Cognito
|
|
- **Nested claims**: e.g., `resource_access.client.roles` for Keycloak
|
|
|
|
### Group Validation
|
|
|
|
- Only string values are accepted as group names
|
|
- Empty or null groups are filtered out
|
|
- Group names are stored as-is (case-sensitive)
|
|
- Maximum reasonable limit of groups per user (no strict limit enforced)
|
|
|
|
## Integration Examples
|
|
|
|
### Role-Based Access Control
|
|
|
|
External applications can query user groups for access control:
|
|
|
|
```bash
|
|
# Get user groups via API
|
|
curl -H "Authorization: Bearer $API_KEY" \
|
|
https://headscale.example.com/api/v1/user/alice | \
|
|
jq '.user.groups[]'
|
|
|
|
# Check if user has admin group
|
|
curl -H "Authorization: Bearer $API_KEY" \
|
|
https://headscale.example.com/api/v1/user/alice | \
|
|
jq '.user.groups | contains(["admin"])'
|
|
```
|
|
|
|
### Database Queries
|
|
|
|
For direct database access:
|
|
|
|
```sql
|
|
-- Get all users with their groups
|
|
SELECT name, email, groups FROM users WHERE provider = 'oidc';
|
|
|
|
-- Find users in specific group
|
|
SELECT name FROM users
|
|
WHERE provider = 'oidc'
|
|
AND JSON_EXTRACT(groups, '$') LIKE '%"admin"%';
|
|
|
|
-- Count users by group membership
|
|
SELECT
|
|
json_each.value as group_name,
|
|
COUNT(*) as user_count
|
|
FROM users, json_each(users.groups)
|
|
WHERE provider = 'oidc'
|
|
GROUP BY json_each.value;
|
|
```
|
|
|
|
## WebUI Integration
|
|
|
|
### Headplane Integration
|
|
|
|
When using Headplane as a web interface, the groups information enables:
|
|
|
|
- **Automatic Role Assignment**: Map OIDC groups to Headplane roles
|
|
- **Dynamic Permissions**: Update user capabilities based on current group membership
|
|
- **Audit Trails**: Track role assignments based on group changes
|
|
|
|
Example Headplane API usage:
|
|
|
|
```javascript
|
|
// Fetch user with groups
|
|
const response = await fetch('/api/v1/user/alice', {
|
|
headers: { 'Authorization': `Bearer ${apiKey}` }
|
|
});
|
|
const user = await response.json();
|
|
|
|
// Map groups to roles
|
|
const roles = mapGroupsToRoles(user.groups);
|
|
console.log(`User ${user.name} has roles:`, roles);
|
|
```
|
|
|
|
## Backward Compatibility
|
|
|
|
### CLI Users
|
|
|
|
- Users created via CLI (`headscale users create`) have empty groups array
|
|
- No changes to existing CLI user management
|
|
- Groups field is optional and defaults to empty array
|
|
|
|
### API Compatibility
|
|
|
|
- All existing API endpoints continue to work unchanged
|
|
- New `groups` field is additive (doesn't break existing clients)
|
|
- Clients can safely ignore the groups field if not needed
|
|
|
|
### Migration
|
|
|
|
- Existing OIDC users will have empty groups until next login
|
|
- No database migration required for basic functionality
|
|
- Groups are populated automatically on subsequent OIDC logins
|
|
|
|
## Configuration
|
|
|
|
### Headscale Configuration
|
|
|
|
Ensure groups scope is included for group extraction:
|
|
|
|
```yaml
|
|
oidc:
|
|
issuer: "https://your-provider.com"
|
|
client_id: "headscale"
|
|
client_secret: "your-secret"
|
|
scope: ["openid", "profile", "email", "groups"]
|
|
```
|
|
|
|
### Provider-Specific Configuration
|
|
|
|
=== "Keycloak"
|
|
```yaml
|
|
# Keycloak automatically includes groups in standard format
|
|
oidc:
|
|
scope: ["openid", "profile", "email", "groups"]
|
|
```
|
|
|
|
=== "Azure AD"
|
|
```yaml
|
|
# Azure AD requires group claims configuration
|
|
oidc:
|
|
scope: ["openid", "profile", "email"]
|
|
# Groups included via claims configuration in Azure AD
|
|
```
|
|
|
|
=== "Okta"
|
|
```yaml
|
|
# Okta supports groups in tokens
|
|
oidc:
|
|
scope: ["openid", "profile", "email", "groups"]
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
### Common Issues
|
|
|
|
**Groups not appearing:**
|
|
- Verify `groups` scope is included in OIDC configuration
|
|
- Check identity provider group claim configuration
|
|
- Ensure user is member of groups in identity provider
|
|
|
|
**Invalid group data:**
|
|
- Non-string group values are automatically filtered out
|
|
- Empty arrays are valid (user has no groups)
|
|
- Malformed JSON is handled gracefully with empty array fallback
|
|
|
|
### Error Responses
|
|
|
|
Standard HTTP error codes apply:
|
|
|
|
- `401 Unauthorized`: Invalid or missing API key
|
|
- `404 Not Found`: User does not exist
|
|
- `500 Internal Server Error`: Server-side processing error
|
|
|
|
## Security Considerations
|
|
|
|
### Access Control
|
|
|
|
- Groups information is available to any client with valid API key
|
|
- Consider creating read-only API keys for external integrations
|
|
- Audit API key usage for compliance requirements
|
|
|
|
### Data Privacy
|
|
|
|
- Group names may contain sensitive organizational information
|
|
- Consider data classification for group membership information
|
|
- Implement appropriate access controls for group data
|
|
|
|
### Token Security
|
|
|
|
- Groups are extracted from verified OIDC tokens only
|
|
- Token validation ensures groups cannot be spoofed
|
|
- Groups are updated only during successful authentication
|
|
|
|
## Monitoring and Observability
|
|
|
|
### Metrics
|
|
|
|
Monitor group extraction and updates:
|
|
|
|
- Number of OIDC logins with groups extracted
|
|
- Distribution of group membership across users
|
|
- Failed group extractions or parsing errors
|
|
|
|
### Logging
|
|
|
|
Key log events to monitor:
|
|
|
|
- Group extraction from OIDC claims
|
|
- Group updates during user authentication
|
|
- Group parsing errors or validation failures
|
|
|
|
Example log entries:
|
|
|
|
```
|
|
INFO Groups extracted from OIDC claims user=alice groups=["admin","developers"]
|
|
WARN Invalid group value filtered out user=bob value=123 type=number
|
|
ERROR Failed to parse groups from OIDC claims user=charlie error="invalid JSON"
|
|
```
|
|
|
|
This API enhancement provides the foundation for implementing sophisticated role-based access control systems while maintaining backward compatibility with existing deployments. |