Skip to content

Commit e31ead1

Browse files
authored
feat: add configurable field-level restrictions for container and pod overrides (#1653)
Signed-off-by: Anatolii Bazko <abazko@redhat.com>
1 parent 3bbd19a commit e31ead1

26 files changed

Lines changed: 4077 additions & 73 deletions

apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright (c) 2019-2025 Red Hat, Inc.
2+
// Copyright (c) 2019-2026 Red Hat, Inc.
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
55
// You may obtain a copy of the License at
@@ -146,6 +146,24 @@ type RoutingConfig struct {
146146
TLSCertificateConfigmapRef *ConfigmapReference `json:"tlsCertificateConfigmapRef,omitempty"`
147147
}
148148

149+
// OverrideConfig defines configuration options for controlling which fields are restricted
150+
// in `container-overrides` and `pod-overrides` DevWorkspace attributes.
151+
// Entries support value-level restrictions: "fieldName" restricts the field entirely,
152+
// while "fieldName=value" restricts only that specific value (other values remain allowed).
153+
type OverrideConfig struct {
154+
// RestrictedContainerOverrideFields defines a list of container-level fields that are restricted
155+
// in `container-overrides` attributes. Note that the following fields are always implicitly
156+
// restricted and cannot be permitted: `name`, `image`, `command`, `args`, `ports`, `env`.
157+
// +kubebuilder:validation:Optional
158+
RestrictedContainerOverrideFields []string `json:"restrictedContainerOverrideFields,omitempty"`
159+
160+
// RestrictedPodOverrideFields defines a list of pod-level fields that are restricted
161+
// in `pod-overrides` attributes. Note that the following fields are always implicitly
162+
// restricted and cannot be permitted: `containers`, `initContainers`.
163+
// +kubebuilder:validation:Optional
164+
RestrictedPodOverrideFields []string `json:"restrictedPodOverrideFields,omitempty"`
165+
}
166+
149167
type WorkspaceConfig struct {
150168
// ProjectCloneConfig defines configuration related to the project clone init container
151169
// that is used to clone git projects into the DevWorkspace.
@@ -264,6 +282,9 @@ type WorkspaceConfig struct {
264282
// InitContainers defines a list of Kubernetes init containers that are automatically injected into all workspace pods.
265283
// Typical uses cases include injecting organization tools/configs, initializing persistent home, etc.
266284
InitContainers []corev1.Container `json:"initContainers,omitempty"`
285+
// Overrides defines configuration options for `container-overrides` and
286+
// `pod-overrides` DevWorkspace attributes.
287+
Overrides *OverrideConfig `json:"overrides,omitempty"`
267288
}
268289

269290
type WebhookConfig struct {

apis/controller/v1alpha1/zz_generated.deepcopy.go

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controllers/workspace/devworkspace_controller.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"time"
2424

2525
"github.com/devfile/devworkspace-operator/pkg/library/initcontainers"
26+
"github.com/devfile/devworkspace-operator/pkg/library/overrides"
2627
"github.com/devfile/devworkspace-operator/pkg/library/ssh"
2728

2829
dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
@@ -338,6 +339,7 @@ func (r *DevWorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request
338339
workspace.Config.Workspace.DefaultContainerResources,
339340
workspace.Config.Workspace.ContainerResourceCaps,
340341
workspace.Config.Workspace.PostStartTimeout,
342+
overrides.GetRestrictedContainerOverrideFields(workspace),
341343
postStartDebugTrapSleepDuration,
342344
)
343345
if err != nil {

deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deploy/deployment/kubernetes/combined.yaml

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deploy/deployment/openshift/combined.yaml

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/dwo-configuration.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,3 +310,94 @@ config:
310310
### Execution Order
311311

312312
Custom init containers are injected after the project-clone init container in the order they are defined in the configuration. The `init-persistent-home` container runs in this sequence along with other custom init containers.
313+
314+
## Always-restricted override fields
315+
316+
Regardless of configuration, certain fields are **always** rejected in overrides and
317+
cannot be unblocked by any configuration change.
318+
319+
**Container overrides:** `name`, `image`, `command`, `args`, `ports`, `env`
320+
321+
**Pod overrides:** `containers`, `initContainers`
322+
323+
These implicit restrictions exist separately from the configurable restricted fields
324+
described below.
325+
326+
## Restricting override fields
327+
328+
The DevWorkspace Operator allows cluster administrators to restrict which fields
329+
can be set via `pod-overrides` and `container-overrides` attributes.
330+
331+
The restrict list supports two formats:
332+
333+
- `"fieldName"` -- restricts the field entirely, regardless of value
334+
- `"fieldName=value"` -- restricts only a specific value for the field
335+
336+
For nested fields such as securityContext or volumes, use dot notation: `securityContext.privileged=true`.
337+
338+
On Kubernetes, the operator ships with default restricted fields that align
339+
with the Pod Security Standards baseline profile.
340+
On OpenShift, no fields are restricted by default since Security Context Constraints (SCC)
341+
already enforce security policies at the admission level.
342+
343+
**Important:** Configuring `restrictedContainerOverrideFields` or `restrictedPodOverrideFields`
344+
**replaces** the platform defaults entirely. Admins who want to extend the default
345+
restrict list must re-include the default entries alongside any additional restrictions.
346+
347+
**Limitation for plain boolean fields in pod overrides:**
348+
Some `PodSpec` fields such as `hostNetwork`, `hostPID`, and `hostIPC` are plain `bool`
349+
types in the Kubernetes API (not `*bool` pointers). Because Go zero-initializes
350+
unset `bool` fields to `false`, the operator cannot distinguish between a field that
351+
was explicitly set to `false` and one that was simply omitted. As a result, using the
352+
bare field name format (e.g. `"hostNetwork"`) to restrict these fields entirely will reject
353+
**all** pod overrides, including those that never mention the field. To avoid this,
354+
use the value-specific format instead (e.g. `"hostNetwork=true"`). The default restricted
355+
fields already follow this pattern. This limitation does not affect `*bool` pointer
356+
fields (e.g. `automountServiceAccountToken`, `shareProcessNamespace`, `hostUsers`)
357+
or non-boolean fields.
358+
359+
For example, on Kubernetes, to add `volumeMounts` and `lifecycle` restrictions
360+
while keeping the default restricted container fields:
361+
362+
```yaml
363+
apiVersion: controller.devfile.io/v1alpha1
364+
kind: DevWorkspaceOperatorConfig
365+
metadata:
366+
name: devworkspace-operator-config
367+
config:
368+
workspace:
369+
overrides:
370+
restrictedContainerOverrideFields:
371+
# Default Kubernetes restricted fields (must be re-listed to retain them)
372+
- "securityContext.privileged=true"
373+
- "securityContext.runAsNonRoot=false"
374+
- "securityContext.runAsUser=0"
375+
- "securityContext.allowPrivilegeEscalation=true"
376+
- "securityContext.procMount=Unmasked"
377+
- "securityContext.capabilities.add"
378+
# Additional restrictions
379+
- "volumeMounts"
380+
- "lifecycle"
381+
```
382+
383+
Similarly, to extend the default restricted pod override fields on Kubernetes:
384+
385+
```yaml
386+
apiVersion: controller.devfile.io/v1alpha1
387+
kind: DevWorkspaceOperatorConfig
388+
metadata:
389+
name: devworkspace-operator-config
390+
config:
391+
workspace:
392+
overrides:
393+
restrictedPodOverrideFields:
394+
# Default Kubernetes restricted fields (must be re-listed to retain them)
395+
- "hostNetwork=true"
396+
- "hostPID=true"
397+
- "hostIPC=true"
398+
- "securityContext.runAsNonRoot=false"
399+
- "securityContext.runAsUser=0"
400+
- "volumes.hostPath"
401+
# Additional restrictions
402+
- "hostUsers=false"
403+
```

0 commit comments

Comments
 (0)