frappe-operator

Security Context Bug Fix - Implementation Summary

Date: January 15, 2026
Status: ✅ Completed and Tested
Severity: High (Critical for hardened clusters)

Problem

The frappe-operator was creating initialization jobs with incomplete security contexts:

Solution Overview

Implemented security context defaults compatible with OpenShift and configurable per installation.

OpenShift-Compatible Defaults

The operator now defaults to OpenShift’s standard security model:

This matches the Frappe container’s expected UID (1001) and OpenShift’s arbitrary UID mechanism.

Configuration Options

The operator provides three levels of UID/GID configuration, with clear priority:

1. Per-Resource Override (Highest 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.

2. Operator-Level Defaults (Environment Variables) ⚙️

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.

3. Hardcoded Defaults (Fallback) 🔒

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.

Configuration Priority

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.

Implementation Details

Default Values

const (
    DefaultUID     int64 = 1001  // OpenShift standard arbitrary UID
    DefaultGID     int64 = 0     // Root group for OpenShift compatibility
    DefaultFSGroup int64 = 0     // Root group for filesystem access
)

Priority Chain

  1. User Override - spec.security in FrappeBench CR (highest priority)
  2. Environment Variables - Operator deployment environment
  3. Hardcoded Defaults - OpenShift-compatible defaults (1001/0/0)

Modified Files

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

Testing

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

Deployment Examples

Example 1: OpenShift (Default) 🎯

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

Example 2: Custom Cluster-Wide UID 🌍

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"

Example 3: Mixed UIDs in Same Cluster 🔀

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

Example 4: Kustomize Overlay for Different Environments 📦

# 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"

Breaking Changes

None. The changes are fully backward compatible:

Why UID 1001 and GID 0?

OpenShift Security Model:

Frappe Container Compatibility:

Security Guarantees

✅ 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

  1. Build and test: make docker-build IMG=vyogo.tech/frappe-operator:v0.1.0-security-fix
  2. Validate in test cluster: Deploy to minikube/kind with Pod Security Policy restricted
  3. Update installation docs to reference new security defaults
  4. Add integration tests to verify security context application