> ## Documentation Index
> Fetch the complete documentation index at: https://docs.casebender.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Deploy to OpenShift

> Deploy CaseBender on Red Hat OpenShift 4.x with the restricted-v2 overlay

<Warning>
  This page is the CaseBender OpenShift installation profile. It is **not** a Red
  Hat certification, OperatorHub listing, or production-certified procedure for
  every OpenShift minor release. Record the exact OpenShift, CNI, CSI, and
  registry versions with your validation output. Use the
  [supported on-premises Compose guide](/en/quickstart) when you are not running
  on OpenShift.
</Warning>

<Info>
  Object storage on OpenShift Data Foundation (ODF) or Ceph RGW is documented
  separately: [OpenShift ODF and Ceph RGW Storage](/en/deployment/storage-openshift-odf-rgw).
  Follow that page only after this overlay is customized, or use the composed
  `k8s/overlays/openshift-odf-rgw` profile instead of local `/data` PVC storage.
</Info>

## Overview

This guide deploys CaseBender on **OpenShift 4.x** using the Kustomize overlay
in `k8s/overlays/openshift`. The overlay is built from `k8s/base` and is
designed for the platform **`restricted-v2` SCC**:

* arbitrary non-root UIDs (no fixed `runAsUser`)
* no privilege escalation; all capabilities dropped
* `RuntimeDefault` seccomp
* read-only root filesystems
* no custom SCC (`anyuid`, privileged, or host access)

Do **not** apply `k8s/base` directly on OpenShift. Ingress is replaced by
**Routes**, placeholder Secrets are stripped from the render, and each
container gets an `emptyDir` at `/tmp`.

```
SIEM / analysts
        │ HTTPS
        ▼
OpenShift Route (edge TLS)  ──►  web, /api/v1, /api/v1/ingest
        │
        ├── webapp, api, ingestion
        ├── worker, workflow-processor, connector-worker
        ├── misp-processor, search-sync
        └── external PostgreSQL + Redis (+ object storage or RWX PVC)
```

## Prerequisites

1. An OpenShift 4.x cluster with `NetworkPolicy` enforcement (OpenShift SDN or
   OVN-Kubernetes).
2. `oc` (and `kubectl` with Kustomize, or `oc kustomize`).
3. Cluster-admin (or equivalent) to prepare the namespace, quotas, registry
   trust, and Routes.
4. **External** PostgreSQL 14+ with TLS, and a Redis-compatible queue with TLS.
   This overlay does not operate the database or Redis lifecycle.
5. DNS for the Route host and TLS terminated at the OpenShift router (edge).
6. Signed, digest-pinned CaseBender images (and SBOMs) in a registry the
   cluster can pull. Production overlays must use **digests**, not floating
   tags.
7. A secrets mechanism (External Secrets Operator, sealed secrets, or an
   approved injector). The rendered overlay contains **no** Secret objects.

Optional: a connected transfer host if the cluster is disconnected.

## What the overlay deploys

| Workload             | Role                                                    |
| -------------------- | ------------------------------------------------------- |
| `webapp`             | Next.js UI, tRPC, Pub/Sub push URL, attachment routes   |
| `api`                | REST `/api/v1`                                          |
| `ingestion`          | Connector ingest `/api/v1/ingest`                       |
| `worker`             | Queues, storage mutation, checklist reconciliation, SLA |
| `workflow-processor` | Workflow engine                                         |
| `connector-worker`   | Private outbound connector actions                      |
| `misp-processor`     | MISP sync (scale-to-zero friendly)                      |
| `search-sync`        | Search index (singleton by default)                     |

Platform objects: namespace `casebender`, Routes, NetworkPolicies, ResourceQuota,
LimitRange, PodDisruptionBudgets, and (default profile) an RWX PVC `casebender-data`
mounted at `/data` on web.

See [Integration Execution Plane](/en/deployment/integration-execution-plane)
for connector-worker secrets and queue names.

## Step 1: Install the OpenShift CLI

<CodeGroup>
  ```bash macOS theme={null}
  brew install openshift-cli
  oc login --server=https://api.cluster.example.com:6443
  oc whoami
  ```

  ```bash Linux theme={null}
  # Follow Red Hat's current OpenShift CLI install for your version:
  # https://docs.redhat.com/en/documentation/openshift_container_platform
  oc login --server=https://api.cluster.example.com:6443
  oc whoami
  ```

  ```powershell Windows theme={null}
  # Install the oc binary from the OpenShift web console (Command Line Tools)
  # or from Red Hat's CLI package for your cluster version.
  oc login --server=https://api.cluster.example.com:6443
  oc whoami
  ```
</CodeGroup>

Confirm Kustomize:

```bash theme={null}
kubectl kustomize --help
# or
oc kustomize --help
```

## Step 2: Prepare the cluster

### Namespace and SCC

The overlay creates namespace `casebender`. Workloads must run as
`restricted-v2` with an **arbitrary** UID:

```bash theme={null}
oc -n casebender auth can-i use scc/restricted-v2 \
  --as=system:serviceaccount:casebender:default
```

Do not grant `anyuid` or a custom SCC to make a fixed UID work. Images must
run as an arbitrary UID, keep writable paths group `0` / `g=u`, listen on an
unprivileged port, and write only to `/tmp` or `/data`.

### Storage choice

Pick **one**:

| Profile        | Overlay                          | When to use                                                      |
| -------------- | -------------------------------- | ---------------------------------------------------------------- |
| Local volume   | `k8s/overlays/openshift`         | Single-writer or RWX CSI that allows arbitrary UIDs; `/data` PVC |
| ODF / Ceph RGW | `k8s/overlays/openshift-odf-rgw` | Customer-managed ODF; three existing buckets; no `/data` PVC     |

The default overlay sets `STORAGE_PROVIDER=local` and `STORAGE_PATH=/data`.
The PVC StorageClass **must** support `ReadWriteMany` if you run more than one
web replica. If it does not, use object storage and the ODF overlay.

CaseBender never creates buckets. ODF/RGW steps:
[OpenShift ODF and Ceph RGW Storage](/en/deployment/storage-openshift-odf-rgw).

### ClamAV

Production user uploads stay quarantined until an external **clamd** scan.
Compose bundles ClamAV; OpenShift does not. Provide `CLAMD_HOST` / TLS or
`CLAMD_SOCKET_PATH` (sidecar). If the scanner is missing, attachments return
HTTP 409 until scan/promotion succeeds.

### Network

Replace bootstrap `0.0.0.0/0` CIDRs in `k8s/overlays/openshift/network-policy.yaml`
with approved ranges for PostgreSQL, Redis, registry, identity, storage, and
connectors **before** production. The ODF overlay additionally restricts RGW
egress; do not restore wildcard egress.

## Step 3: Customize the overlay

Copy `k8s/overlays/openshift` (or `openshift-odf-rgw`) into an
**environment-specific** directory. Do not commit customer hostnames or
secrets into the product repository.

1. **Images** — in `kustomization.yaml`, replace every
   `registry.example.com/casebender/...` and `replace-with-release` with the
   mirrored **digest**.
2. **Route host** — in `route.yaml`, set `spec.host` on all Routes to the
   approved DNS name (default placeholder `casebender.apps.example.com`).
   Paths: `/` (web), `/api/v1` (API), `/api/v1/ingest` and `/api/ingest`
   (ingestion), `/api/openapi.json` and `/docs` (API docs).
3. **PVC** (local storage profile) — in `platform-controls.yaml`, set
   `storageClassName` and size.
4. **ConfigMap** — non-secret settings (`casebender-config`): public URL,
   storage provider, feature flags. TLS endpoints only in production.
5. **NetworkPolicy** — approved CIDRs only.

## Step 4: Provision secrets

The overlay **deletes** the base placeholder Secret from the render. Create
`casebender-secrets` in namespace `casebender` **before** the first rollout.

`k8s/overlays/openshift/external-resources.example.yaml` is an External Secrets
example (excluded from Kustomize). Adapt it to your SecretStore. Typical keys:

* `POSTGRES_PRISMA_URL`
* `REDIS_URL`
* `AUDIT_INTEGRITY_SECRET`
* `FIELD_ENCRYPTION_KEY`
* `CREDENTIAL_ENCRYPTION_KEY`
* `WEBHOOK_KEY_PEPPER`
* `CONNECTOR_BUNDLE_SIGNING_KEY`
* `OAUTH_BROKER_SECRET`

Preserve `AUDIT_INTEGRITY_SECRET` across upgrades. Rotating it invalidates the
audit chain. Never put secret values in ConfigMaps, Kustomize patches, or git.

Confirm the Secret exists and the render has no Secret objects:

```bash theme={null}
oc -n casebender get secret casebender-secrets
kubectl kustomize k8s/overlays/openshift | grep -c '^kind: Secret' || true
```

## Step 5: Render, validate, and apply

From the repository root (or your copied overlay tree):

```bash theme={null}
kubectl kustomize k8s/overlays/openshift \
  > /tmp/casebender-openshift.yaml

./scripts/openshift/validate.sh /tmp/casebender-openshift.yaml

oc apply --server-side --dry-run=server \
  -f /tmp/casebender-openshift.yaml

oc apply --server-side \
  -f /tmp/casebender-openshift.yaml

oc -n casebender rollout status deployment --timeout=10m
```

For ODF/RGW storage, render `k8s/overlays/openshift-odf-rgw` instead (it
composes the OpenShift overlay). Provision ObjectBucketClaims **before**
application startup. The adapter never creates buckets.

Web runs database migrations on startup. You do not run Prisma by hand.

## Step 6: Verify

```bash theme={null}
oc -n casebender get pods,route,networkpolicy,pdb,pvc
oc -n casebender logs deploy/webapp --tail=100
oc adm policy scc-subject-review -f /tmp/casebender-openshift.yaml
```

Then confirm:

* every pod has a non-zero arbitrary UID and `restricted-v2`
* writes to the root filesystem fail; `/tmp` (and `/data` if used) succeed
* Routes serve HTTPS only; HTTP redirects
* default-deny NetworkPolicy blocks unapproved traffic
* `/api/health/live` and `/api/health/ready` behave as documented in
  [Storage Health and Troubleshooting](/en/deployment/storage-health-troubleshooting)
* a test upload is SHA-256 verified and, with clamd, leaves quarantine

Archive the rendered manifest, image digests, `validate.sh` output, and SCC
review with the change record. Cluster execution is required before calling a
release “OpenShift validated.”

## Disconnected registry

1. On a connected host, download images, checksums, signatures, and SBOMs.
2. Verify, then scan the mirrored digest under your policy.
3. `oc image mirror` (or the approved tool) **by digest**.
4. Configure `ImageDigestMirrorSet` / `ImageTagMirrorSet` if required; wait for
   MachineConfigPool convergence.
5. Create a namespace pull secret and link it for pull. Do not commit it.

```bash theme={null}
oc -n casebender create secret docker-registry casebender-registry \
  --docker-server=registry.internal.example.com \
  --docker-username="$REGISTRY_USER" \
  --docker-password="$REGISTRY_PASSWORD"
oc -n casebender secrets link default casebender-registry --for=pull
```

Re-render and confirm every image is the mirror digest. Do not fall back to a
public registry.

## Proxy and custom CA

Cluster-wide proxy is a cluster-admin change (`oc edit proxy/cluster`). If
workloads need `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY`, include cluster
domains, service and pod CIDRs, PostgreSQL, Redis, and storage. Add an egress
rule for the proxy CIDR and port; the bootstrap policy does not open common
proxy ports.

For an internal CA, mount only the CA file (do not replace the image trust
store):

```bash theme={null}
oc -n casebender create configmap casebender-custom-ca \
  --from-file=ca-bundle.crt=organization-ca.pem \
  --dry-run=client -o yaml | oc apply -f -
```

Mount read-only at
`/etc/pki/ca-trust/source/anchors/casebender-ca.crt` (`subPath`) and set
`NODE_EXTRA_CA_CERTS` to that path. Restart and test TLS to PostgreSQL, Redis,
storage, OIDC, and connectors. Never disable TLS verification as a CA
workaround.

## Backup, upgrade, and rollback

Back up a consistency set: PostgreSQL, object storage (or `/data` snapshot),
secret **versions** (not plaintext in the archive), rendered manifest, and
Route/NetworkPolicy. See
[Storage Backup and Restore](/en/deployment/storage-backup-restore).

Before upgrade, save live objects and the target render:

```bash theme={null}
oc -n casebender get all,pvc,networkpolicy,pdb,resourcequota -o yaml \
  > casebender-pre-upgrade.yaml
kubectl kustomize k8s/overlays/openshift > casebender-target.yaml
```

Roll stateless workers before externally exposed services. Application rollback
is allowed only while the database and object format stay backward compatible.
If a non-reversible migration ran, restore; do not point old images at a new
schema.

## Troubleshooting

1. **Pods crash with permission denied / SCC**\
   Confirm `restricted-v2`, no fixed UID, group-writable image paths, and
   `/tmp` emptyDir.

2. **Route 503 / no backends**\
   Check probes, Service port (`webapp` often 80 → container), and rollout.

3. **Attachments HTTP 409 / “Image unavailable”**\
   Worker malware scanner not reachable, or storage mutation not running.
   Restore clamd; objects stay quarantined until a clean verdict.

4. **Readiness 503 / storage**\
   Local PVC vs ODF mismatch, missing buckets, or TLS/CA. Use
   [Storage Health](/en/deployment/storage-health-troubleshooting).

5. **Audit errors after upgrade**\
   `AUDIT_INTEGRITY_SECRET` changed. Restore the previous value.

## Related documentation

* Overlay runbook in the repository: `k8s/overlays/openshift/README.md`
* [OpenShift ODF and Ceph RGW Storage](/en/deployment/storage-openshift-odf-rgw)
* [Storage Overview](/en/deployment/storage-overview)
* [Upgrading](/en/deployment/upgrading)
* [Integration Execution Plane](/en/deployment/integration-execution-plane)
