Files
grafana/pkg/services/scimutil/README.md
colin-stuart b6eacc929a Auth: Check SCIM dynamic settings when syncing users (#107487)
* Auth: Check SCIM dynamic settings when syncing users

* remove enterprise package import

* add CODEOWNERS

* lint

* use default namespace & update tests

* add optional dynamic AllowNonProvisionedUsers setting

* update test case with allowNonProvisionedUsers
2025-07-02 15:23:06 -05:00

181 lines
5.8 KiB
Markdown

# SCIM Utility
This package provides utility functions for checking SCIM dynamic app platform settings using the `client.K8sHandler`. It allows both the `authimpl` and `saml` packages to check SCIM settings with dynamic configuration support and static fallback.
## API Reference
### SCIMUtil
The main utility struct that provides methods for checking SCIM settings.
```go
type SCIMUtil struct {
k8sClient client.K8sHandler
logger log.Logger
}
```
### Methods
#### NewSCIMUtil
Creates a new SCIMUtil instance.
```go
func NewSCIMUtil(k8sClient client.K8sHandler) *SCIMUtil
```
#### IsUserSyncEnabled
Checks if SCIM user sync is enabled using dynamic configuration with static fallback.
```go
func (s *SCIMUtil) IsUserSyncEnabled(ctx context.Context, orgID int64, staticEnabled bool) bool
```
#### AreNonProvisionedUsersAllowed
Checks if non-provisioned users are allowed using dynamic configuration with static fallback.
```go
func (s *SCIMUtil) AreNonProvisionedUsersAllowed(ctx context.Context, orgID int64, staticAllowed bool) bool
```
**Note**: This field defaults to `false` when not present in the dynamic configuration.
## Usage
### Basic Usage
```go
import (
"context"
"github.com/grafana/grafana/pkg/services/apiserver/client"
"github.com/grafana/grafana/pkg/services/scimutil"
)
// Create a new SCIM utility instance
scimUtil := scimutil.NewSCIMUtil(k8sClient)
// Check if user sync is enabled (with dynamic config support)
userSyncEnabled := scimUtil.IsUserSyncEnabled(ctx, orgID, staticConfig.IsUserProvisioningEnabled)
// Check if non-provisioned users are allowed (with dynamic config support)
nonProvisionedAllowed := scimUtil.AreNonProvisionedUsersAllowed(ctx, orgID, staticConfig.AllowNonProvisionedUsers)
```
### In authimpl Package
The `authimpl` package uses this utility in the `UserSync` struct to check SCIM settings during user provisioning validation:
```go
// In user_sync.go
type UserSync struct {
// ... other fields ...
scimUtil *scim_util.SCIMUtil
staticConfig *StaticSCIMConfig
}
func (s *UserSync) skipProvisioningValidation(ctx context.Context, currentIdentity *authn.Identity) bool {
// Use dynamic SCIM settings if available, otherwise fall back to static config
effectiveUserSyncEnabled := s.isUserProvisioningEnabled
effectiveAllowNonProvisionedUsers := s.allowNonProvisionedUsers
if s.scimUtil != nil {
orgID := currentIdentity.GetOrgID()
effectiveUserSyncEnabled = s.scimUtil.IsUserSyncEnabled(ctx, orgID, s.staticConfig.IsUserProvisioningEnabled)
effectiveAllowNonProvisionedUsers = s.scimUtil.AreNonProvisionedUsersAllowed(ctx, orgID, s.staticConfig.AllowNonProvisionedUsers)
}
// ... rest of validation logic ...
}
```
### In SAML Package
The SAML package can use this utility to check SCIM settings during authentication:
```go
// In saml package
type SCIMHelper struct {
scimUtil *scim_util.SCIMUtil
}
func (h *SCIMHelper) CheckUserSyncEnabled(ctx context.Context, orgID int64, staticEnabled bool) bool {
if h.scimUtil == nil {
return staticEnabled
}
return h.scimUtil.IsUserSyncEnabled(ctx, orgID, staticEnabled)
}
```
## Dynamic Configuration
The utility supports dynamic SCIM configuration through the Kubernetes API. It will:
1. First attempt to fetch SCIM settings from the dynamic configuration (SCIMConfig resource)
2. If dynamic configuration is not available or fails, fall back to static configuration from `config.ini`
3. Log the source of configuration being used for debugging
### Configuration Sources
- **Dynamic**: SCIMConfig resource in Kubernetes (org-specific)
- Resource name: `default`
- API Group: `scim.grafana.com/v0alpha1`
- Kind: `SCIMConfig`
- **Static**: `auth.scim` section in `config.ini` (global)
### SCIMConfig Resource Structure
```yaml
apiVersion: scim.grafana.com/v0alpha1
kind: SCIMConfig
metadata:
name: default
namespace: <org-namespace>
spec:
enableUserSync: true # Controls user provisioning
enableGroupSync: false # Controls group/team provisioning
allowNonProvisionedUsers: false # Controls whether non-provisioned users are allowed (optional)
```
## Error Handling
The utility gracefully handles errors and falls back to static configuration when:
- K8s client is not configured
- SCIMConfig resource is not found
- Network errors occur
- Invalid configuration is encountered
- Missing or malformed spec in SCIMConfig resource
All errors are logged for debugging purposes with appropriate log levels:
- `Debug`: Normal operation messages
- `Warn`: Fallback scenarios and non-critical errors
- `Error`: Invalid configuration or unexpected errors
## Implementation Details
This package is designed to work with the open-source Grafana build and does not depend on enterprise-only SCIM API types. It uses a simplified `SCIMConfigSpec` struct that contains only the essential configuration fields:
```go
type SCIMConfigSpec struct {
EnableUserSync bool `json:"enableUserSync"`
EnableGroupSync bool `json:"enableGroupSync"`
AllowNonProvisionedUsers *bool `json:"allowNonProvisionedUsers,omitempty"`
}
```
The `AllowNonProvisionedUsers` field is optional and defaults to `false` when not present in the configuration.
The utility directly works with Kubernetes unstructured objects and extracts the configuration values without requiring the full SCIM API types.
## Testing
The package includes comprehensive tests covering:
- All combinations of user sync, group sync, and non-provisioned users settings
- Error scenarios and fallback behavior
- Integration scenarios with both dynamic and static configurations
- Mock implementations for the K8s client interface
- Optional field handling for `allowNonProvisionedUsers`
Run tests with:
```bash
go test ./pkg/services/scimutil
```