Skip to content

feat(kustomize): add OpenShift components for the restricted-v2 SCC and Routes - #51

Open
pratapalakshmi wants to merge 2 commits into
mainfrom
feat/openshift-support
Open

feat(kustomize): add OpenShift components for the restricted-v2 SCC and Routes#51
pratapalakshmi wants to merge 2 commits into
mainfrom
feat/openshift-support

Conversation

@pratapalakshmi

Copy link
Copy Markdown
Contributor

Kustomize counterpart to plane-ee#9018 (images) and helm-charts#289 (chart).

OpenShift ignores the image's USER, assigns an arbitrary UID from the namespace's range, and validates the pod's own request with MustRunAsRange — so nonroot-security-context, which pins runAsUser/runAsGroup/fsGroup: 1000, is rejected at admission and stops every pod from scheduling. Neither ingress component produces anything the OpenShift router serves either.

Two new opt-in components. Nothing existing changes; no shared file other than the README is touched.

Component What it does
openshift-security-context The same hardening as nonroot-security-contextrunAsNonRoot, capabilities.drop: [ALL], seccompProfile: RuntimeDefault — with no UID named, so the SCC assigns one.
ingress-openshift Removes the base nginx Ingress (inert on OpenShift) and adds one route.openshift.io/v1 Route per path, mirroring base/ingress.yaml. Each carries haproxy.router.openshift.io/timeout: 300s — the router's 30s default severs /live/ WebSockets and /pi/ streaming. Ships its own APP_DOMAIN replacement, so enabling it is the only overlay change needed.
components:
  - ../../components/openshift-security-context   # NOT nonroot-security-context
  - ../../components/ingress-openshift            # NOT ingress-nginx / ingress-traefik

Verification

Built the default overlay with both components swapped in for nonroot-security-context:

  • 11 Routes, host replacement applied (plane.example.com → the overlay's APP_DOMAIN)
  • base nginx Ingress removed
  • no workload requests a fixed uid/gid, except the bundled plane-opensearch

That opensearch exception is not a regression — I built the same overlay with nonroot-security-context and it keeps fsGroup: 1000 / runAsUser: 1000 identically. The datastore components (postgres, redis, rabbitmq, minio, opensearch) are listed after the security-context component in the overlay, so neither component ever patches them. They cannot run under an arbitrary UID regardless, which is why the README says to drop them on OpenShift and use managed services.

Why explicit Routes rather than ingress-to-route

Keeping the base Ingress and setting INGRESS_CLASS: "openshift-default" also works — the README documents it as the alternative. But the conversion only picks up an Ingress whose class maps to openshift.io/ingress-to-route (nginx is ignored), and whether the per-route HAProxy annotations survive varies by OCP version. Plane needs that timeout, so declaring the Routes removes the guesswork.

Routes have no request-body cap — Traefik's plane-body-limit middleware has no equivalent. Path rewriting (e.g. the draw-io component's stripPrefix) needs haproxy.router.openshift.io/rewrite-target instead, and Traefik's redirectRegex has no Route equivalent at all. Both are called out in the component header and README.

Requires

The images from plane-ee#9018, which grant group 0 write access to their runtime paths. Those have been validated on a real cluster under an arbitrary UID — see internal-scripts#122. SCC admission and Route behaviour still need a CRC / OpenShift Local run.

🤖 Generated with Claude Code

…nd Routes

OpenShift ignores the image's USER, assigns an arbitrary UID from the namespace's
range, and validates the pod's own request with MustRunAsRange -- so the
nonroot-security-context component, which pins runAsUser/runAsGroup/fsGroup 1000,
is rejected at admission and prevents every pod from scheduling. Neither ingress
component produces anything the OpenShift router serves either.

Two new components, both drop-in replacements rather than changes to existing
behaviour:

- openshift-security-context: the same hardening as nonroot-security-context
  (runAsNonRoot, capabilities.drop ALL, seccompProfile RuntimeDefault) with no
  UID named, so the SCC assigns one. As with nonroot-security-context, the
  bundled datastores are not patched -- they are listed after it in the overlay
  and cannot run under an arbitrary UID anyway.
- ingress-openshift: removes the base nginx Ingress (inert on OpenShift) and adds
  one route.openshift.io/v1 Route per path, mirroring base/ingress.yaml. Each
  carries haproxy.router.openshift.io/timeout, because the router's 30s default
  severs /live/ WebSockets and /pi/ streaming. Declaring the Routes avoids
  depending on whether annotations survive the ingress-to-route conversion, which
  varies by OCP version. The component carries its own APP_DOMAIN replacement, so
  enabling it is the only overlay change needed.

Verified by building the default overlay with both components swapped in: 11
Routes with the host replacement applied, the base Ingress gone, and no workload
requesting a fixed uid/gid except the bundled opensearch -- which behaves
identically under nonroot-security-context, so this is not a regression.

Nothing existing changes: both are new opt-in components, and no shared file
other than the README is touched.

Requires the images from plane-ee #9018, which grant group 0 write access to the
paths they write at runtime. Chart equivalent: helm-charts #<pending>.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two components alone left every operator to assemble the overlay themselves,
and the obvious move -- copy `default` and toggle components -- does not work:
`default` pins runAsUser 1000 and ships the in-cluster datastores, and OpenShift
rejects both. This adds the assembled overlay so there is a real starting point.

overlays/openshift is `default` with exactly three differences:

  - openshift-security-context instead of nonroot-security-context (no UID named,
    because restricted-v2 assigns one and rejects a pod that asks for a specific
    runAsUser/runAsGroup/fsGroup)
  - ingress-openshift instead of ingress-nginx (base Ingress removed, 11 Routes
    added, each with haproxy.router.openshift.io/timeout: 300s)
  - postgres/redis/rabbitmq/minio/opensearch omitted -- third-party images with
    baked-in UID and data-directory ownership that cannot run under an arbitrary
    UID, so the overlay expects external services

Consequently the storage-class patches (which only ever targeted those five
StatefulSets) are gone, as are the two replacements targeting kind: Ingress --
ingress-openshift carries its own APP_DOMAIN -> Route spec.host replacement.

static-db-url and s3-static-credentials ARE kept. They are about static
credentials vs a cloud workload identity, not about in-cluster vs external:
static-db-url is the only thing that populates plane-silo-secrets.DATABASE_URL,
and s3-static-credentials the only thing that populates the doc-store AWS keys.
Dropping them silently leaves silo without a database and S3 unauthenticated.

vars.yaml.example drops INGRESS_CLASS (Routes carry no class) and defaults
MONITOR_STORAGE_CLASS to gp3-csi rather than the EKS-only gp2.
secrets-vars.yaml.example points every connection string at an external service
and sets USE_MINIO=0.

Verified with `kustomize build`: 11 Routes on the APP_DOMAIN host, 0 Ingress
objects, namespace plane-openshift, no workload requesting a fixed uid/gid,
plane-monitor the only StatefulSet, MONITOR_STORAGE_CLASS flowing through to its
PVC, and DATABASE_URL/AWS keys populated in the app, silo and doc-store secrets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant