MAQPNADocs

Install on Red Hat OpenShift

Install MAQPNA on OpenShift (OCP, ROSA, ARO) step by step, with OpenShift sandboxed containers, the default restricted-v2 SCC, OVN-Kubernetes network policy, a Route for the gateway and an optional attested tier with Trustee.

This page installs MAQPNA on Red Hat OpenShift: OpenShift Container Platform, ROSA, ARO or OpenShift on IBM Cloud. You install OpenShift sandboxed containers for the Kata runtime, install the chart with the values-openshift.yaml profile on top of the production profile, and expose the gateway with a Route. Every MAQPNA pod runs under the default restricted-v2 security context constraint (SCC); you grant no extra SCC.

The generic steps (Secrets, values, OIDC, the PostgreSQL state backend) are in Production install with Helm. This page adds what is specific to OpenShift.

What you get#

Trust tier Runtime on OpenShift RuntimeClass
tier-0 Kata (Red Hat ships no gVisor) kata (bare metal or nested-virt workers), or kata-remote (peer pods on AWS or Azure)
tier-1 (microVM) Kata kata or kata-remote
tier-2 (confidential VM) Kata with confidential containers and the Red Hat build of Trustee kata-remote, commented out in the profile until you deploy it

The profile also:

  • removes the pinned UID, GID and fsGroup 65532 (podSecurityContext.runAsUser: null, and the same for runAsGroup and fsGroup), so OpenShift assigns them from the namespace's range;
  • sets operator.sandboxRunAsUID: -1, so the operator also leaves the UID of sandbox pods to OpenShift;
  • turns off the chart's three RuntimeClasses;
  • puts the gateway's ledger mirror on the cluster's default storage class (gateway.auditStorage.storageClassName: "").

The images run unchanged under an arbitrary UID: the binaries are world-readable and every writable path is a volume owned by the assigned fsGroup.

flowchart LR
  RT["Router<br/>(openshift-ingress)"] -->|"Route, TLS"| GW
  subgraph NS["maqpna-system (restricted-v2)"]
    OP[operator]
    GW["gateway x3"]
    ID[identity broker]
    AT["attestation service"]
  end
  subgraph WK["Kata workers (KataConfig)"]
    S0["tier-0 / tier-1 sandboxes<br/>kata"]
    S2["tier-2 sandboxes<br/>kata-remote + CoCo"]
  end
  S0 -->|"OVN NetworkPolicy"| GW
  S2 --> GW
  S2 -.->|evidence| AT
  AT --> TR["Trustee<br/>(Red Hat build)"]
  GW --> PG[("PostgreSQL")]

Before you begin#

  • oc, helm 3.14 or later, and cluster-admin rights.
  • The maqpna CLI and its maqpna-install plugin, version 2.1.0: curl -fsSL https://maqpna.com/install.sh | sh.
  • REGISTRY set to the release registry listed on maqpna.com/download.
  • Workers that can run Kata: bare metal, or nested virtualization. For peer pods on AWS or Azure, follow Red Hat's peer-pods guide.
V=2.1.0
export REGISTRY=<release-registry-from-maqpna.com/download>

Step 1: Install OpenShift sandboxed containers#

  1. Install OpenShift sandboxed containers from OperatorHub.
  2. Create a KataConfig. To limit Kata to some workers, set kataConfigPoolSelector to a node label.
  3. Wait until the nodes have rebooted and the RuntimeClass exists:
oc get runtimeclass        # expect kata (and kata-remote for peer pods)

For peer pods (kata-remote), edit values-openshift.yaml or your values so tier-0 and tier-1 name kata-remote.

Step 2: Deploy confidential containers (tier-2, optional)#

Follow Red Hat's "Deploying confidential containers" guide for your platform (Azure DCasv5/ECasv5, or bare-metal AMD SEV-SNP or Intel TDX). It adds a Trustee operator, which the MAQPNA attestation service uses as its verifier. Skip this step if you do not need an attested tier.

Step 3: Create the project and install the agent-sandbox controller#

oc new-project maqpna-system
oc apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/v1.0.6/sandbox-with-extensions.yaml

Step 4: Mirror the images (disconnected clusters)#

Connected clusters pull from the release registry. For a disconnected cluster, mirror the images into the internal registry or Quay with oc mirror, or with maqpna airgap as described in Air-gapped install, then set airgap.imageRegistry to the mirror.

Step 5: Create the Secrets and your values file#

helm pull "oci://$REGISTRY/charts/maqpna" --version "$V" --untar
grep -n CHANGE-ME maqpna/values-production.yaml

Create the Secrets as in Production install, step 1. Then write my-values.yaml. To turn on tier-2 once confidential containers and Trustee run, add it to the tier list. Helm replaces lists as a whole, so repeat tier-0 and tier-1:

trustTiers:
  create: true
  items:
    - name: tier-0
      spec: {runtimeClassName: kata, isolation: microvm, egress: gateway-only}
    - name: tier-1
      spec: {runtimeClassName: kata, isolation: microvm, egress: gateway-only}
    - name: tier-2
      spec: {runtimeClassName: kata-remote, isolation: confidential, attestationRequired: true, egress: gateway-only}
attestation:
  enabled: true
  verifier: trustee
  trusteeURL: https://kbs.trustee-operator-system.svc:8080   # your Trustee service
  trusteeJWKS:
    existingSecret: maqpna-trustee-jwks
  referenceValues:
    allowedMeasurements: ["<launch measurement of your guest image>"]
networkPolicy:
  agentNamespaces:
    names: [team-a]

An empty allowedMeasurements list rejects every report (fail closed).

Step 6: Validate, preflight and install#

maqpna values validate --chart ./maqpna --profile production \
  -f maqpna/values-production.yaml -f maqpna/values-openshift.yaml -f my-values.yaml

maqpna preflight --chart ./maqpna --profile prod -f maqpna/values-openshift.yaml -f my-values.yaml

maqpna install --chart ./maqpna --profile prod -f maqpna/values-openshift.yaml -f my-values.yaml \
  --license maqpna-license.jws --wait --timeout 15m

Check that every pod got restricted-v2:

oc get pods -n maqpna-system -o custom-columns=NAME:.metadata.name,SCC:.metadata.annotations.openshift\.io/scc

Step 7: Expose the gateway with a Route#

The chart creates a ClusterIP Service (maqpna-gateway, port 8080) and no Ingress or Route. Create a Route with edge TLS (or passthrough when gateway.tls.enabled terminates TLS at the gateway):

oc -n maqpna-system create route edge maqpna-gateway --service maqpna-gateway --port http \
  --hostname gateway.apps.example.com

The chart's NetworkPolicy admits only agent namespaces and the release namespace. Admit the OpenShift router too:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: maqpna-gateway-from-router
  namespace: maqpna-system
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: maqpna-gateway
  policyTypes: ["Ingress"]
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              policy-group.network.openshift.io/ingress: ""
      ports:
        - {protocol: TCP, port: 8080}

OVN EgressFirewall rules with dnsName apply to a whole namespace; MAQPNA does not render them. The gateway still enforces its own egress allowlist (sovereignty.allowedEgressHosts).

Step 8: Verify#

oc -n maqpna-system get pods
oc get trusttiers
helm test maqpna -n maqpna-system
oc -n maqpna-system port-forward svc/maqpna-gateway 9090:9090 &
maqpna doctor -n maqpna-system --gateway http://127.0.0.1:9090 --oidc-token-file admin.tok --strict
oc get pods -A -o custom-columns=NS:.metadata.namespace,POD:.metadata.name,RC:.spec.runtimeClassName,NODE:.spec.nodeName

With tier-2 on, doctor also checks attest-verifier (must be trustee) and attest-response-signing.

Upgrading from an older guide#

Earlier versions of the OpenShift guide asked you to grant nonroot-v2 to every ServiceAccount. Remove those grants after you move to the profile:

oc adm policy remove-scc-from-user nonroot-v2 -z <service-account> -n maqpna-system

Troubleshooting#

Symptom Cause Fix
Pods rejected: unable to validate against any security context constraint, runAsUser: Invalid value: 65532 The OpenShift profile is missing, so the UID pin is still set Add -f maqpna/values-openshift.yaml.
Sandbox pods rejected for their UID operator.sandboxRunAsUID is not -1 Keep the profile's operator.sandboxRunAsUID: -1.
Sandbox pods Pending: no node matches The kata RuntimeClass schedules only on KataConfig nodes Check kataConfigPoolSelector and that the nodes finished rebooting.
tier-2 sessions fail with reason AttestationFailed Trustee unreachable, wrong JWKS, or measurements not listed Follow the attestation failure runbook.
Route returns 503 The router is blocked by NetworkPolicy Apply the router NetworkPolicy from step 7.
pod has unbound immediate PersistentVolumeClaims No default StorageClass Set gateway.auditStorage.storageClassName to an existing class (ODF, gp3-csi, managed-csi).

More: Troubleshooting.

Next steps#