feat: add PPM OIDC/SSO web UI configuration#112
Conversation
Enable Connect and Workbench to authenticate against PPM using Kubernetes Identity Federation (RFC 8693 token exchange). Adds OIDC and Identity Federation config types for PPM, shared token exchange init container and sidecar helpers, and opt-in AuthenticatedRepos flag on Connect/Workbench specs.
All 8 findings addressed. Build passes and tests pass. Here's the summary: Changes: - Install curl and jq in token exchange script (`apk add --no-cache`) so alpine:3 default image works - Add null/empty token validation after jq extraction to fail fast instead of writing "null" as password - Add `OIDCAudience` field to `SiteSpec` so OIDC audience is configurable (defaults to `sts.amazonaws.com` for backwards compatibility) - Revert `AutomountServiceAccountToken` to `ptr.To(false)` in Connect — projected volume works independently - Add `PPMAuthImage` to `InternalConnectSpec` and `InternalWorkbenchSpec` and propagate from Site controllers - Add gcfg injection validation for `IdentityFederation` Name (reject `"`, `]`, newlines) - Add `cleanupPPMAuthConfigMap` to delete the ConfigMap when authenticated repos feature is disabled - Add `SanitizePPMUrl` helper to strip existing scheme before prepending `https://`, preventing double-prefix
|
Claude finished @ian-flores's task —— View job Review: feat: add PPM authenticated repos via Identity Federation
OverallWell-structured feature. The separation of concerns (shared helpers in Below are findings organized by severity. Critical: Audience mismatch between projected SA token and Identity Federation config
These must match for the token exchange to succeed. If the PPM-side
Important: Shell script installs packages at runtime
Consider using a Posit-owned image with Important: Sidecar exits on transient token refresh failure
Consider: if [ "${MODE}" = "sidecar" ]; then
while true; do
sleep "$REFRESH_INTERVAL"
exchange_token || echo "WARNING: token refresh failed, will retry" >&2
done
fiImportant: Connect sets
|
- Fix audience mismatch: thread OIDCAudience from site spec through Connect/Workbench to projected SA token volumes - Remove runtime apk add from token exchange script (image must have curl+jq pre-installed) - Add sidecar resilience: catch refresh failures instead of dying - Remove unused ppmAuthCurlrcPath constant - Clarify AutomountServiceAccountToken comment re: projected volumes - Add .claude/tsc-cache to .gitignore
Add typed support for all PPM authentication configuration fields: - New [Authentication] section (APITokenAuth, DeviceAuthType, session lifetime, etc.) - 12 new [OpenIDConnect] fields (ClientSecretFile, PKCE, claims customization, token lifetime, etc.) - 9 new [IdentityFederation] fields (claims, groups, roles, logging)
alpine:3 runs as root by default, which fails pod security contexts that require runAsNonRoot. Set RunAsUser=65534 (nobody) on both init and sidecar containers. PPM requires Scope, RoleClaim, or GroupToScopeMapping when OIDC is configured. Default to "repos:read:*" in the site controller.
alpine:3 doesn't include curl or jq, and apk install requires root. Replace curl with wget (BusyBox built-in) and jq with a sed-based JSON field extractor for zero-dependency operation.
The field was tagged json:"-" which prevented it from being stored in the PackageManager CR. The site controller sets it in memory but it was lost when the PM controller read the CR back from the API.
The Name field was tagged json:"-" so it was lost during Kubernetes API serialization. This caused empty section names in the generated gcfg: [IdentityFederation ""] instead of [IdentityFederation "connect"].
PPM treats ClientSecret as a literal value, not a file path. Use ClientSecretFile instead so PPM reads the secret from the mounted file. K8s SA tokens don't have a preferred_username claim. Set UniqueIdClaim and UsernameClaim to "sub" for Identity Federation entries.
All builds pass and tests are green. Findings #1 (PPMAuthImage propagation), #2 (cache files), and #3 (apk add stderr) were already addressed in prior commits. I fixed the two remaining issues. Changes: - `SanitizePPMUrl` now returns empty string for empty input instead of invalid `"https://"` - Added edge case tests for `SanitizePPMUrl`: empty string and URL with port/path
internal/controller/core/ppm_auth.go
Outdated
| // PPMAuthTokenExchangeScript returns the shell script content for the token exchange | ||
| // init container and sidecar. The script exchanges a K8s service account token for a | ||
| // PPM API token via RFC 8693 token exchange, then writes a netrc file and a curlrc | ||
| // file (so R's libcurl can also authenticate via --netrc-file). |
There was a problem hiding this comment.
This pattern is where I'd like more input on the review
|
This is a pretty big PR. Looking at the parts that relate to PTDC-224, that looks pretty standard/good. Is it possible to put a separate PR for that piece in first and then follow up with some discussion on connect+workbench integration to focus our discussion on the pros/cons of that part? |
Remove Connect/Workbench authenticated repos (Identity Federation) code to a separate PR per review feedback. This PR now contains only the PPM OIDC/SSO web UI configuration (PTDC-224).
|
Done! I've split this PR to only contain the OIDC/SSO web UI config (PTDC-224). The Connect/Workbench authenticated repos piece is now in #114 as a separate draft PR — happy to discuss the approach there. |
|
This is a pretty interesting idea overall. Do you have any suggestions or paths for RStudio or Positron desktop user support with oidc+package manager (and do you think the magic-ness of relegates those users in desktop environments? I'd also be super curious for some input from @jstruzik + team on the use of this pattern for integrating ppm oidc with workbench and connect. is this a direction that feels natural and worth of documentation outside of team-operator-land? do you have a preferred means of integration on the way that we should maybe make use of instead? |
Team Operator users won't have that problem though...
I think this is for PR #114? |
Description
Enable OIDC authentication for the Package Manager web UI. Adds first-class OIDC configuration types and full typed coverage of all PPM authentication config fields from the PPM configuration reference.
When
auth.type: oidcis set on the Site'spackageManagerspec, the operator automatically configures the[OpenIDConnect]and[Authentication]gcfg sections and mounts the OIDC client secret from the vault.Issue
Follow-up
Connect/Workbench authenticated repos via Identity Federation is in #114.
Code Flow
Key Components
PPM Authentication Config Types (
package_manager_config.go): NewPackageManagerAuthenticationConfigandPackageManagerOIDCConfigstructs with full field coverage from the PPM configuration reference. ExtendedGenerateGcfg()to emit[Authentication]and[OpenIDConnect]sections.OIDC Client Secret Mount (
packagemanager_types.go): Mounts the OIDC client secret at/etc/rstudio-pm/oidc-client-secretvia SecretProviderClass (AWS) or K8s Secret volume (K8s).Site Controller Propagation (
site_controller_package_manager.go): Propagates OIDC config from Site spec to PackageManager, settingClientSecretFile,Issuer,ClientId,RequireLogin, andScope.SecretProviderClass (
package_manager.go): Adds OIDC client secret to the SecretProviderClass whenOIDCClientSecretKeyis configured.SiteSpec Example
Testing
Tested end-to-end on
ganso01-staging(main site) with adhoc imageadhoc-ppm-auth-repos-v1.16.1-15-gedc6aaa.Setup
positrealm withppmclient (confidential, authorization code grant type)/etc/rstudio-pm/oidc-client-secretResults
[OpenIDConnect]section withClientSecretFile[Authentication]section/__login__→ 302 to Keycloak auth endpoint)/etc/rstudio-pm/oidc-client-secretNote on repo authentication
PPM's OIDC redirect only activates when repos are configured as "Authenticated". The
[OpenIDConnect]section enables the OIDC provider, but individual repos must be marked as authenticated (viarspm edit repo --name=<repo> --authenticatedor creating repos with--authenticatedflag). This is expected PPM behavior — unauthenticated repos remain accessible without login.Category of change
Checklist
just testand all tests pass