Skip to content

Rootless#879

Open
ssinnott wants to merge 15 commits into
mainfrom
rootless
Open

Rootless#879
ssinnott wants to merge 15 commits into
mainfrom
rootless

Conversation

@ssinnott

@ssinnott ssinnott commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

So idea here is to wire up a rootless version of workbench in the helm charts,

  1. There's a simple high level flag (pod. runAsRoot) this defaults to true (for now). But you can set it to false to basically wire in a pretty separate mode for workbench. Where,
    a. sssd is disabled - you need to use SCIM instead.
    b. Workbench and pods that it launches will use the service account instead.
    c. There's some other toggling which is more minor.

  2. If you're in the rootless mode you can also specify the service account user and uid. For this to really work though I think you would need to build your own version of the workbench image. The user + uid is baked into there with my other changes.

Closes https://github.com/rstudio/rstudio-pro/issues/10847

ssinnott added 8 commits June 1, 2026 15:27
- Run Workbench as non-root by default via serviceAccountUser
- Invoke supervisord with -u "" when running Workbench non-root
- Fix supervisord non-root workaround: pass -u <serviceAccountUser> instead of -u ""
- Fix non-root supervisord startup: emptyDir, path rewriting, fsGroup
- Simplify prestart-launcher.bash for non-root operation
- Simplify prestart-workbench.bash for non-root operation
- Update secrets test mode assertions for non-root default (0640)
- Document rootless prestart script simplification
- Drop supervisord non-root workaround, use image default command
- Add explicit config.sssd toggle, off by default
- Add non-root config defaults for launcher auth, PAM, provisioning
- Default serviceAccountUser to root, keep non-root opt-in
- Avoid contradictory root securityContext on user override
Previously sssd.enabled defaulted to false with a hard render failure
if set to true while running non-root. Now sssd.enabled defaults to true
and is silently skipped when serviceAccountUser is not root, preserving
existing root behavior with no BREAKING change.

Introduce rstudio-workbench.sssd.active helper to centralize the
effective-SSSD gate (enabled AND root) used across helpers, configmaps,
and NOTES.txt warnings.
…tUser

Add top-level runAsRoot: true (default) as the single binary control for
pod privilege. Previously, serviceAccountUser == "root" was the implicit
trigger for all root-vs-non-root behavior (securityContext, SSSD, secret
file modes, rserver.conf defaults), conflating the OS application user
with the Kubernetes security concept.

serviceAccountUser now solely controls server-user in rserver.conf.
All privilege-gating logic switches from eq serviceAccountUser "root"
to .Values.runAsRoot / not .Values.runAsRoot.
@ssinnott ssinnott requested review from GCRev and zachhannum as code owners June 2, 2026 15:31
pod:
securityContext:
runAsUser: 4242
fsGroup: 4242

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Recommend making this unique, even if that's unrealistic, just to be sure these values end up where they are supposed to.

Suggested change
fsGroup: 4242
fsGroup: 4243

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

good idea!

value: 4242
- equal:
path: spec.template.spec.securityContext.fsGroup
value: 4242

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
value: 4242
value: 4243

@GCRev GCRev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If there are any problems with this, then I didn't spot them.

When load balancing is active (replicas > 1 or loadBalancer.forceEnabled),
prestart-workbench.bash writes the load-balancer config under
/mnt/load-balancer. With no volume mounted there the path lives on the
container root fs, which a non-root pod (pod.runAsRoot: false) cannot
write to, causing CrashLoopBackOff on mkdir.

Mount an emptyDir at /mnt/load-balancer under the same condition that
sets RSW_LOAD_BALANCING, mirroring the /mnt/dynamic pattern. fsGroup
makes it writable for the unprivileged user.
@ssinnott ssinnott requested a review from a team June 8, 2026 18:56
Comment thread charts/rstudio-workbench/NEWS.md Outdated
Comment on lines +7 to +8
- A new `config.sssd` block controls the bundled SSSD daemon (replaces the deprecated `config.userProvisioning`).
- `config.userProvisioning` is deprecated; use `config.sssd.conf`. A warning is emitted when it is set.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Consider rewriting these two lines into a single bullet point; they feel kind of redundant. I'm also confused by config.sssd vs config.sssd.conf.

Comment thread charts/rstudio-workbench/NEWS.md Outdated
## 0.21.1

- Workbench pods can now run as a non-root user. Set `pod.runAsRoot: false` (new value, default `true`) to run unprivileged.
- When running unprivelaged SCIM must be used for user management.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
- When running unprivelaged SCIM must be used for user management.
- Use SCIM for user management when running unprivileged.

Comment thread charts/rstudio-workbench/NEWS.md Outdated
- When running unprivelaged SCIM must be used for user management.
- A new `config.sssd` block controls the bundled SSSD daemon (replaces the deprecated `config.userProvisioning`).
- `config.userProvisioning` is deprecated; use `config.sssd.conf`. A warning is emitted when it is set.
- The Workbench and launcher prestart scripts (`prestart-workbench.bash`, `prestart-launcher.bash`) no longer perform root-only operations.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How relevant is this to the end admin? I'm not familiar enough to know if this is important to call out or if it could be dropped.

Comment thread charts/rstudio-workbench/NEWS.md Outdated
- A new `config.sssd` block controls the bundled SSSD daemon (replaces the deprecated `config.userProvisioning`).
- `config.userProvisioning` is deprecated; use `config.sssd.conf`. A warning is emitted when it is set.
- The Workbench and launcher prestart scripts (`prestart-workbench.bash`, `prestart-launcher.bash`) no longer perform root-only operations.
- A writable `emptyDir` is now mounted at `/mnt/load-balancer` when load balancing is active (`replicas > 1` or `loadBalancer.forceEnabled: true`), so the prestart script can write the load-balancer config when the pod runs unprivileged (`pod.runAsRoot: false`).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
- A writable `emptyDir` is now mounted at `/mnt/load-balancer` when load balancing is active (`replicas > 1` or `loadBalancer.forceEnabled: true`), so the prestart script can write the load-balancer config when the pod runs unprivileged (`pod.runAsRoot: false`).
- A writable `emptyDir` is now mounted at `/mnt/load-balancer` when load balancing is active (`replicas > 1` or `loadBalancer.forceEnabled: true`). The prestart script writes the load-balancer config in this directory when the pod runs unprivileged (`pod.runAsRoot: false`).

(Or perhaps "can write" instead of "writes"; I don't know the details.)

- Located at:<br> `config.userProvisioning.<< name of file >>` Helm values
- SSSD Configuration:
- These configuration files configure the bundled `sssd` daemon (legacy LDAP/AD provisioning), which is started only when `config.sssd.enabled=true`.
- Located at:<br> `config.sssd.conf.<< name of file >>` Helm values (the deprecated `config.userProvisioning` is honored as a fallback)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This line seems really awkward... I'm not sure what you mean.

The [latest Workbench container](https://github.com/rstudio/rstudio-docker-products/tree/main/workbench#user-provisioning)
has `sssd` included and running by default (see `userProvisioning` configuration files above).
Posit Workbench's native user provisioning (SCIM / just-in-time) is the recommended approach and does not require SSSD.
The legacy approach is `sssd`: the [latest Workbench container](https://github.com/rstudio/rstudio-docker-products/tree/main/workbench#user-provisioning)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
The legacy approach is `sssd`: the [latest Workbench container](https://github.com/rstudio/rstudio-docker-products/tree/main/workbench#user-provisioning)
`sssd` is the legacy approach: the [latest Workbench container](https://github.com/rstudio/rstudio-docker-products/tree/main/workbench#user-provisioning)

|-----|------|---------|-------------|
| affinity | object | `{}` | A map used verbatim as the pod's "affinity" definition |
| args | list | `[]` | args is the pod container's run arguments. |
| args | list | `[]` | args is the pod container's run arguments. When unset, the container's default arguments are used. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Consider changing the content of the "default" column. Not sure what to suggest though.

Comment on lines +36 to +37
* A method to join the deployed `rstudio-workbench` container to your auth domain. The `posit/workbench` image ships `sssd` for legacy LDAP/Active Directory provisioning. SSSD must run as root, so the bundled daemon starts by default only on root deployments (`config.sssd.enabled: true`, `pod.runAsRoot: true`) and is automatically skipped when `pod.runAsRoot: false`. Modern provisioning (SCIM / native) does not require SSSD.
Provide its configuration in `config.sssd.conf` like so (set `config.sssd.enabled: false` to disable the daemon entirely):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder if this is too much information?

@adamhigerd

Copy link
Copy Markdown
Contributor

I'm going to recuse myself from this PR beyond basic documentation style. I don't really know much about this subject matter and I'm heading out of town tomorrow.

ssinnott added 2 commits June 8, 2026 20:49
On the rootless path (pod.runAsRoot: false) the launcher session pods were
left with the default launcher-sessions-create-container-user=1, which needs
the session container to start as root to useradd + setuid into the requesting
user. An unprivileged container cannot, so sessions crashed with EPERM.

Disabling it makes rserver stamp the session pod securityContext with the
user's UID/GID instead (the runAsUserId branch in ServerJobLauncher), so the
session runs directly as the user with no privilege.
The Kubernetes launcher runs as the non-root serviceAccountUser even in a
root pod, so several root-mode paths broke once it stopped running as root:

- Set fsGroup (and fsGroupChangePolicy: OnRootMismatch) in root mode when
  launcher.enabled, so the projected ServiceAccount token is group-readable
  (root:rstudio-server 0640) to the launcher (rstudio-pro#11349).
- Project the autogenerated secure-cookie-key into /mnt/secret-configmap/rstudio
  in rootless mode so the launcher shares rserver's cookie key instead of
  reading a missing file.
- Create the launcher scratch dirs (Local, Kubernetes) setgid + group-writable
  so the non-root launcher can write to them without a root-only chown.
Comment thread charts/rstudio-workbench/NEWS.md Outdated
- The launcher prestart script now creates its scratch dirs (`/var/lib/rstudio-launcher/Local`, `/Kubernetes`) setgid and group-writable, so the launcher (which runs as the non-root `serviceAccountUser`) can write to them in root-mode pods without a privileged ownership change.

- Workbench pods can now run as a non-root user. Set `pod.runAsRoot: false` (new value, default `true`) to run unprivileged.
- When running unprivelaged SCIM must be used for user management.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I still don't fully understand how SCIM works here but JIT doesn't. Fundamentally it seems like the requirement is that Workbench native user provisioning must be used, not necessarily the exact provisioning method?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah! Yeah I'm conflating the two here. I'll be clearer. This work with SCIM as well as JIT. You need to use openid/saml or some other auth provider - local auth basically doesn't work because that leans on pam.

# `false` runs unprivileged: pod sets `runAsNonRoot: true` and `runAsUser`/`fsGroup` to
# `pod.serviceAccountUserId`, SSSD is skipped, secrets mount 0640, and non-root
# `rserver.conf`/`launcher.conf` defaults are applied.
runAsRoot: true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is making this true by default a breaking change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

By default we currently run as root - flipping this to false will enabled the rootless mode. Which is beaking.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤦 Duh. Ignore me.

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.

4 participants