Description
When a DevWorkspace sets controller.devfile.io/scc, DWO creates a namespaced devworkspace-use-<scc>
Role and RoleBinding granting the workspace ServiceAccount use on that SCC.
Changing the attribute after it has been set is rejected
(... attribute cannot be modified after being set -- workspace must be deleted), but removing it
is allowed, and nothing cleans up the RBAC that was created for it.
Three separate code paths gate on the same "is the attribute present?" check, so once it is removed all
three quietly skip:
webhook/workspace/handler/access_control.go:33 — validateUserPermissions returns early when the
new spec has no SCC attribute, so removal is never compared against the previous value.
pkg/provision/workspace/rbac/role.go:35 — syncRoles returns before it can reconcile the SCC Role
away.
pkg/provision/workspace/rbac/finalize.go:27 — FinalizeRBAC only calls finalizeSCCRBAC while the
attribute is present, so deleting the workspace skips SCC cleanup entirely.
The result is a Role and RoleBinding left in the namespace, still referencing the workspace
ServiceAccount, with no remaining workspace that would ever trigger cleanup.
This is a cleanup/lifecycle gap rather than an access-control one — see Additional context below.
How To Reproduce
Needs OpenShift, a non-privileged user who holds use on anyuid, and the DWO controller
ServiceAccount holding the SCC cluster-wide (a namespaced add-scc-to-user is not sufficient, since
the Role is created in the workspace namespace).
Setup, as cluster-admin — OPERATOR_NS is where DWO runs, OPENSHIFT_USER is the non-privileged
user, NS is their namespace:
OPERATOR_NS=devworkspace-controller
OPENSHIFT_USER=developer
NS=dw-test
oc adm policy add-cluster-role-to-user system:openshift:scc:anyuid -z devworkspace-controller-serviceaccount -n $OPERATOR_NS
oc create role use-anyuid --verb=use --resource=securitycontextconstraints --resource-name=anyuid -n $NS
oc adm policy add-role-to-user use-anyuid $OPENSHIFT_USER --role-namespace=$NS -n $NS
-
As the non-privileged user, create a workspace requesting the SCC. started: true is required —
the Role is only created during provisioning.
kubectl apply -n $NS -f - <<EOF
kind: DevWorkspace
apiVersion: workspace.devfile.io/v1alpha2
metadata:
name: scc-leak
spec:
started: true
routingClass: basic
template:
attributes:
controller.devfile.io/scc: anyuid
components:
- name: dev
container:
image: quay.io/wto/web-terminal-tooling:latest
args: ["tail", "-f", "/dev/null"]
memoryLimit: 512Mi
EOF
-
As cluster-admin, confirm DWO created the RBAC — both objects are present:
oc get role,rolebinding devworkspace-use-anyuid -n $NS
-
As the non-privileged user, remove the attribute. The patch is accepted:
kubectl patch dw scc-leak -n $NS --type json -p '[{"op":"remove","path":"/spec/template/attributes/controller.devfile.io~1scc"}]'
-
As the non-privileged user, delete the workspace:
kubectl delete dw scc-leak -n $NS
-
As cluster-admin, the Role and RoleBinding are still present, with no workspaces left in the
namespace:
oc get role,rolebinding devworkspace-use-anyuid -n $NS
oc get rolebinding devworkspace-use-anyuid -n $NS -o jsonpath='{.subjects}'
oc get dw -n $NS
The binding still references the deleted workspace's ServiceAccount, e.g.
[{"kind":"ServiceAccount","name":"workspace9a52dbfdefd34a29-sa","namespace":"..."}].
Expected behavior
Either:
- removal of
controller.devfile.io/scc is rejected once the attribute has been set, consistent with
how modification is already handled; or
- removal is allowed, and the corresponding
devworkspace-use-<scc> Role and RoleBinding are removed
along with it (subject to the existing check for other workspaces in the namespace still using that
SCC).
Either way, deleting the workspace should not leave SCC RBAC behind.
Additional context
Not an access-control issue. Re-adding an SCC attribute later goes through the full
LocalSubjectAccessReview again — both the previous attribute and any validated-SCC record are gone at
that point — so nobody gains access they were not already entitled to. The impact is stale RBAC
accumulating in workspace namespaces, bound to ServiceAccounts that no longer exist.
Consistency with existing intent. The comment above the modification check
(access_control.go:55) gives the rationale as "Don't allow attribute to be changed once it is set,
otherwise we can't clean up the SCC when the workspace is deleted." Removal is exactly that
uncleaned case, so this looks more like an unhandled path than a deliberate decision.
Version. Reproduced on main (OpenShift 4.22.1, CRC). Noticed while reviewing #1693, which
rewrites validateUserPermissions in this area but does not change this behaviour — the early return
is present on main as well, which is why this is filed separately.
Description
When a DevWorkspace sets
controller.devfile.io/scc, DWO creates a namespaceddevworkspace-use-<scc>Role and RoleBinding granting the workspace ServiceAccount
useon that SCC.Changing the attribute after it has been set is rejected
(
... attribute cannot be modified after being set -- workspace must be deleted), but removing itis allowed, and nothing cleans up the RBAC that was created for it.
Three separate code paths gate on the same "is the attribute present?" check, so once it is removed all
three quietly skip:
webhook/workspace/handler/access_control.go:33—validateUserPermissionsreturns early when thenew spec has no SCC attribute, so removal is never compared against the previous value.
pkg/provision/workspace/rbac/role.go:35—syncRolesreturns before it can reconcile the SCC Roleaway.
pkg/provision/workspace/rbac/finalize.go:27—FinalizeRBAConly callsfinalizeSCCRBACwhile theattribute is present, so deleting the workspace skips SCC cleanup entirely.
The result is a Role and RoleBinding left in the namespace, still referencing the workspace
ServiceAccount, with no remaining workspace that would ever trigger cleanup.
This is a cleanup/lifecycle gap rather than an access-control one — see Additional context below.
How To Reproduce
Needs OpenShift, a non-privileged user who holds
useonanyuid, and the DWO controllerServiceAccount holding the SCC cluster-wide (a namespaced
add-scc-to-useris not sufficient, sincethe Role is created in the workspace namespace).
Setup, as cluster-admin —
OPERATOR_NSis where DWO runs,OPENSHIFT_USERis the non-privilegeduser,
NSis their namespace:As the non-privileged user, create a workspace requesting the SCC.
started: trueis required —the Role is only created during provisioning.
As cluster-admin, confirm DWO created the RBAC — both objects are present:
oc get role,rolebinding devworkspace-use-anyuid -n $NSAs the non-privileged user, remove the attribute. The patch is accepted:
As the non-privileged user, delete the workspace:
kubectl delete dw scc-leak -n $NSAs cluster-admin, the Role and RoleBinding are still present, with no workspaces left in the
namespace:
The binding still references the deleted workspace's ServiceAccount, e.g.
[{"kind":"ServiceAccount","name":"workspace9a52dbfdefd34a29-sa","namespace":"..."}].Expected behavior
Either:
controller.devfile.io/sccis rejected once the attribute has been set, consistent withhow modification is already handled; or
devworkspace-use-<scc>Role and RoleBinding are removedalong with it (subject to the existing check for other workspaces in the namespace still using that
SCC).
Either way, deleting the workspace should not leave SCC RBAC behind.
Additional context
Not an access-control issue. Re-adding an SCC attribute later goes through the full
LocalSubjectAccessReviewagain — both the previous attribute and any validated-SCC record are gone atthat point — so nobody gains access they were not already entitled to. The impact is stale RBAC
accumulating in workspace namespaces, bound to ServiceAccounts that no longer exist.
Consistency with existing intent. The comment above the modification check
(
access_control.go:55) gives the rationale as "Don't allow attribute to be changed once it is set,otherwise we can't clean up the SCC when the workspace is deleted." Removal is exactly that
uncleaned case, so this looks more like an unhandled path than a deliberate decision.
Version. Reproduced on
main(OpenShift 4.22.1, CRC). Noticed while reviewing #1693, whichrewrites
validateUserPermissionsin this area but does not change this behaviour — the early returnis present on
mainas well, which is why this is filed separately.