Skip to content

feat: add optional SCIM provisioning support for Workbench#120

Merged
timtalbot merged 4 commits intomainfrom
scim-provisioning
Apr 1, 2026
Merged

feat: add optional SCIM provisioning support for Workbench#120
timtalbot merged 4 commits intomainfrom
scim-provisioning

Conversation

@timtalbot
Copy link
Copy Markdown
Contributor

@timtalbot timtalbot commented Mar 31, 2026

Description

Adds opt-in SCIM user provisioning for Workbench, allowing an external IdP (e.g. Okta, Entra ID) to manage users and groups via the Workbench SCIM v2 API without requiring first-login.

API — new scim block on Site.spec.workbench:

spec:
  workbench:
    scim:
      enabled: true
      tokenSecretName: my-secret  # optional — omit to auto-generate

Operator behaviour when scim.enabled: true:

  • Generates a cryptographically random bearer token (32 bytes, base64url) and stores it in a managed Secret named <site-name>-workbench-scim-token in posit-team. The token is never rotated automatically; deleting the Secret triggers regeneration on the next reconcile.
  • Alternatively, point tokenSecretName at a pre-existing Secret with a token key (BYO mode). The operator uses it as-is and does not manage its lifecycle.
  • Mounts the token into the Workbench pod at /etc/rstudio/scim-token via a Secret volume.
  • Sets WORKBENCH_USER_SERVICE_AUTH_TOKEN_PATH=/etc/rstudio/scim-token on the Workbench container so Workbench can locate the token at startup.

With this in place, Workbench enables the https://<workbench-hostname>/scim/v2/Users endpoint for IDPs to call. I was able to test with direct curl calls to this endpoint, simulating what an IDP would do, creating users, editing their attributes, creating groups, and cleaning it all up.

Retrieve the token to configure your IdP:

kubectl get secret <site-name>-workbench-scim-token \
  -n posit-team -o jsonpath='{.data.token}' | base64 -d

Configure your IdP SCIM endpoint as https://<workbench-hostname>/scim/v2 with Bearer token auth.

Code Flow

File Change
api/core/v1beta1/site_types.go WorkbenchSCIMConfig struct; SCIM field on InternalWorkbenchSpec
api/core/v1beta1/workbench_types.go SCIM field on WorkbenchSpec
internal/controller/core/site_controller_workbench.go Propagate site.Spec.Workbench.SCIMtargetWorkbench.Spec.SCIM
internal/controller/core/workbench.go reconcileSCIMToken, buildSCIMTokenEnvVars, buildSCIMTokenVolumeMounts, buildSCIMTokenVolumes
internal/controller/core/workbench_test.go 4 new tests covering disabled, managed token, BYO token, and no-rotation cases
config/crd/bases/, client-go/, zz_generated.deepcopy.go Regenerated

Category of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Build: a code change that affects the build system or external dependencies
  • Performance: a code change that improves performance
  • Refactor: a code change that neither fixes a bug nor adds a feature
  • Documentation: documentation changes
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist

  • I have run just test and all tests pass
  • I have reviewed my own diff and added inline comments on lines I want reviewers to focus on or that I am uncertain about

@claude
Copy link
Copy Markdown

claude bot commented Mar 31, 2026

Claude finished @timtalbot's task —— View job


Review: SCIM Provisioning for Workbench

  • Read review guidelines
  • Analyze PR diff and changed files
  • Create and submit review

Well-structured feature with clear separation between managed and BYO modes. The reconciler logic is idempotent and the test coverage is solid. A few items to consider:


Security — BYO secret key validation (workbench.go:1362-1370)

The BYO secret is fetched but never validated for the token key. If a user provides a secret without a token key, the volume mount will succeed but produce an empty/missing file. Consider validating:

if _, ok := byoSecret.Data["token"]; !ok {
    l.Info("WARNING: BYO SCIM token secret exists but is missing the 'token' key", "secretName", w.Spec.SCIM.TokenSecretName)
}

Race condition on managed secret creation (workbench.go:1408-1410)

If two reconcile loops fire concurrently, both could pass the IsNotFound check and attempt Create. Handle IsAlreadyExists on the create call (same pattern as package_manager.go:265):

if err := r.Create(ctx, secret); err != nil {
    if kerrors.IsAlreadyExists(err) {
        l.Info("SCIM token secret was created concurrently, reusing", "secretName", managedSecretName)
        return managedSecretName, nil
    }
    return "", fmt.Errorf("error creating SCIM token secret %q: %w", managedSecretName, err)
}

One-way SCIM propagation (site_controller_workbench.go:458-461)

The Site→Workbench propagation only copies when SCIM != nil. If a user removes the scim block from the Site spec, the Workbench retains its previous config. For a security-sensitive feature, consider explicitly clearing it:

// Propagate SCIM config
targetWorkbench.Spec.SCIM = site.Spec.Workbench.SCIM

This makes the nil case propagate correctly as well.

API validation (site_types.go:503)

TokenSecretName has no +kubebuilder:validation annotation. Consider adding a pattern for valid Kubernetes names (^[a-z0-9]([-a-z0-9]*[a-z0-9])?$) to give users an early admission error rather than a reconcile-time failure.

Guard clause repetition (workbench.go:1348,1419,1432,1447)

The w.Spec.SCIM == nil || !w.Spec.SCIM.Enabled check appears 4 times with slightly different variants. A small helper like scimEnabled(w) or a method on the Workbench type would reduce repetition and guard divergence.

SubPath mount note (workbench.go:1439)

SubPath mounts are not auto-updated by kubelet. This is fine for the current "no auto-rotation" design, but worth noting for anyone who later considers rotation — they'd need to drop the SubPath.

Test coverage gaps (workbench_test.go)

The four tests cover the main cases well. Two gaps to consider:

  • Disable-after-enable: Enable SCIM, reconcile, then set Enabled = false and reconcile again. Verify mounts/envs are removed from the deployment spec.
  • Invalid BYO secret: If key validation is added, test a secret missing the token key.

Self-review

Per repo norms — no inline comments from the author on this PR. Consider annotating key decision points (e.g. the SubPath choice, the no-rotation design) to help reviewers focus.


@timtalbot
Copy link
Copy Markdown
Contributor Author

@claude re-review

@claude
Copy link
Copy Markdown

claude bot commented Apr 1, 2026

Claude finished @timtalbot's task —— View job


Re-review: SCIM Provisioning for Workbench

  • Read current state of changed files
  • Compare against previous review feedback
  • Submit updated review

All five actionable items from the first review have been addressed:

Previous Feedback Status
BYO secret key validation Fixed — returns hard error at workbench.go:1365-1367
Race condition on managed secret creation FixedIsAlreadyExists handled at workbench.go:1407-1410
One-way SCIM propagation Fixed — unconditional assignment at site_controller_workbench.go:459
API validation for TokenSecretName Fixed+kubebuilder:validation:Pattern added at site_types.go:503
Guard clause repetition Fixed — extracted scimEnabled() helper at workbench.go:1418-1420

Test coverage has also been expanded (6 tests, up from 4):

  • TestWorkbenchSCIM_DisableAfterEnable — verifies mounts/envs are removed after disabling
  • TestWorkbenchSCIM_BYOTokenMissingKey — verifies hard error on missing token key

One remaining observation (non-blocking):

Managed secret cleanup on disable (workbench.go:1348): When SCIM is disabled after being enabled, the managed secret persists as an orphan. The OwnerReferences will GC it when the Workbench is deleted, but if SCIM is toggled off while the Workbench lives on, the secret remains. This is fine for now — the no-rotation design means leftover secrets are harmless — but worth documenting if you plan to add rotation later.

Otherwise, this looks good to merge.


View job

@timtalbot timtalbot marked this pull request as ready for review April 1, 2026 12:04
@timtalbot timtalbot requested review from amdove and stevenolen April 1, 2026 12:05
Copy link
Copy Markdown
Collaborator

@stevenolen stevenolen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking good!

@timtalbot timtalbot added this pull request to the merge queue Apr 1, 2026
@timtalbot timtalbot removed this pull request from the merge queue due to a manual request Apr 1, 2026
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to Branch Protection failures Apr 1, 2026
You're not authorized to push to this branch. Visit "About protected branches" for more information.
@timtalbot timtalbot added this pull request to the merge queue Apr 1, 2026
Merged via the queue into main with commit 531e45f Apr 1, 2026
9 checks passed
@timtalbot timtalbot deleted the scim-provisioning branch April 1, 2026 21:01
ian-flores pushed a commit that referenced this pull request Apr 1, 2026
# [1.22.0](v1.21.1...v1.22.0) (2026-04-01)

### Features

* add optional SCIM provisioning support for Workbench ([#120](#120)) ([531e45f](531e45f))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants