Date: January 15, 2026
Status: ✅ Completed and Tested
Severity: High (Critical for hardened clusters)
The frappe-operator was creating initialization jobs with incomplete security contexts:
runAsGroup: 0 and fsGroup: 0 without runAsUserfailed to make sandbox docker config for pod: runAsGroup is specified without a runAsUserImplemented security context defaults compatible with OpenShift and configurable per installation.
The operator now defaults to OpenShift’s standard security model:
RunAsUser: 1001 (arbitrary UID in OpenShift’s default range)RunAsGroup: 0 (root group for arbitrary UID support)FSGroup: 0 (root group for filesystem permissions)This matches the Frappe container’s expected UID (1001) and OpenShift’s arbitrary UID mechanism.
The operator provides three levels of UID/GID configuration, with clear priority:
Override security context for individual FrappeBench or FrappeSite resources:
apiVersion: vyogo.tech/v1alpha1
kind: FrappeBench
metadata:
name: custom-bench
spec:
security:
podSecurityContext:
runAsUser: 2000 # Custom UID for this bench only
runAsGroup: 2000 # Custom GID
fsGroup: 2000 # Custom FSGroup
securityContext:
runAsUser: 2000
runAsGroup: 2000
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
Use case: Different benches need different UIDs in the same cluster.
Set cluster-wide defaults by configuring the operator deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: frappe-operator-controller-manager
namespace: frappe-operator-system
spec:
template:
spec:
containers:
- name: manager
image: vyogo.tech/frappe-operator:latest
env:
- name: FRAPPE_DEFAULT_UID
value: "2000" # Changes default for ALL benches
- name: FRAPPE_DEFAULT_GID
value: "2000" # Changes default for ALL benches
- name: FRAPPE_DEFAULT_FSGROUP
value: "2000" # Changes default for ALL benches
Use case: Organization-wide security policy requires specific UID/GID.
When no override is specified:
RunAsUser: 1001 (OpenShift standard arbitrary UID)
RunAsGroup: 0 (Root group for OpenShift compatibility)
FSGroup: 0 (Root group for filesystem permissions)
Use case: OpenShift deployments or standard Frappe container images.
spec.security (per-resource)
↓
Environment Variables (operator-level)
↓
Hardcoded Defaults (1001/0/0)
Higher levels override lower levels. If spec.security is set, environment variables and defaults are ignored.
const (
DefaultUID int64 = 1001 // OpenShift standard arbitrary UID
DefaultGID int64 = 0 // Root group for OpenShift compatibility
DefaultFSGroup int64 = 0 // Root group for filesystem access
)
spec.security in FrappeBench CR (highest priority)| File | Changes |
|---|---|
| controllers/frappebench_resources.go | Updated security context helpers to use configurable defaults |
| controllers/frappesite_controller.go | Updated security context helpers to use configurable defaults |
| controllers/utils.go | Added getDefaultUID(), getDefaultGID(), getDefaultFSGroup(), getEnvAsInt64() |
| controllers/security_context_test.go | 8 unit tests validating defaults and overrides |
✅ Unit Tests: All 8 security context tests pass
✅ Build Tests: Go compilation successful
✅ OpenShift Compatibility: UID 1001 with GID 0 (arbitrary UID pattern)
✅ Environment Configuration: Validated with FRAPPE_DEFAULT_* env vars
No configuration needed - defaults work out of the box:
# Just deploy the operator, it uses UID 1001 / GID 0 automatically
kubectl apply -f https://github.com/vyogotech/frappe-operator/releases/latest/download/install.yaml
Change defaults for the entire cluster:
apiVersion: apps/v1
kind: Deployment
metadata:
name: frappe-operator-controller-manager
namespace: frappe-operator-system
spec:
template:
spec:
containers:
- name: manager
image: vyogo.tech/frappe-operator:latest
env:
- name: FRAPPE_DEFAULT_UID
value: "1000" # All benches default to UID 1000
- name: FRAPPE_DEFAULT_GID
value: "1000"
- name: FRAPPE_DEFAULT_FSGROUP
value: "1000"
Some benches need different UIDs:
# Bench 1: Uses operator defaults (1001/0/0)
apiVersion: vyogo.tech/v1alpha1
kind: FrappeBench
metadata:
name: production-bench
spec:
# No security section = uses defaults
apps:
- name: erpnext
---
# Bench 2: Custom UID for compliance requirements
apiVersion: vyogo.tech/v1alpha1
kind: FrappeBench
metadata:
name: compliance-bench
spec:
security:
podSecurityContext:
runAsUser: 5000 # Override just for this bench
runAsGroup: 5000
fsGroup: 5000
apps:
- name: erpnext
# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
- patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: frappe-operator-controller-manager
namespace: frappe-operator-system
spec:
template:
spec:
containers:
- name: manager
env:
- name: FRAPPE_DEFAULT_UID
value: "2000" # Production uses UID 2000
- name: FRAPPE_DEFAULT_GID
value: "2000"
None. The changes are fully backward compatible:
spec.security continue to work unchangedOpenShift Security Model:
Frappe Container Compatibility:
USER 1001✅ Non-root user execution (UID 1001 ≠ 0)
✅ Privilege escalation prevented
✅ All capabilities dropped
✅ Seccomp runtime default profile
✅ PSP/Pod Security Standards compliant
✅ OpenShift restricted SCC compatible
make docker-build IMG=vyogo.tech/frappe-operator:v0.1.0-security-fixrestricted