Chapter 3. Configure


You can configure confidential containers on bare-metal servers with the Intel® Trust Domain Extensions (TDX) and AMD SEV-SNP Trusted Execution Environments (TEEs).

Perform the following steps:

  1. Configure worker nodes so that TEEs are automatically detected.
  2. Intel® TDX: Configure the remote attestation infrastructure.
  3. Enable confidential containers.
  4. Create initdata to initialize a pod with sensitive or workload-specific data at runtime.

    Important

    Do not use the default permissive Kata Agent policy in a production environment. You must configure a restrictive policy, preferably by creating initdata.

    As a minimum requirement, you must disable ExecProcessRequest to prevent a cluster administrator from accessing sensitive data by running the oc exec command on a confidential containers pod.

  5. Add initdata to a pod manifest.
  6. Create the KataConfig CR.
  7. Verify the attestation process.
  8. Configure your workload for confidential containers.

3.1. Auto-detecting TEEs

You must label your worker nodes so that the OpenShift sandboxed containers Operator can detect the Trusted Execution Environments (TEEs).

You label the nodes by installing and configuring the Node Feature Discovery (NFD) Operator.

You create a NodeFeatureDiscovery custom resource (CR) to define the configuration parameters that the Node Feature Discovery (NFD) Operator checks to automatically detect your TEE.

Prerequisites

Procedure

  1. Create a my-nfd.yaml manifest file according to the following example:

    apiVersion: nfd.openshift.io/v1
    kind: NodeFeatureDiscovery
    metadata:
      name: nfd-instance
      namespace: openshift-nfd
    spec:
      operand:
        image: registry.redhat.io/openshift4/ose-node-feature-discovery-rhel9:v4.21
        imagePullPolicy: Always
        servicePort: 12000
      workerConfig:
        configData: |
  2. Create the NodeFeatureDiscovery CR:

    $ oc create -f my-nfd.yaml

Create a NodeFeatureRule custom resource for your Trusted Execution Environment (TEE).

Procedure

  1. Create a custom resource manifest named my-nodefeaturerule.yaml:
apiVersion: nfd.openshift.io/v1alpha1
kind: NodeFeatureRule
metadata:
  name: consolidated-hardware-features
  namespace: openshift-nfd
spec:
  rules:
    - name: "runtime.kata"
      labels:
        feature.node.kubernetes.io/runtime.kata: "true"
      matchAny:
        - matchFeatures:
            - feature: cpu.cpuid
              matchExpressions:
                SSE42: { op: Exists }
                VMX: { op: Exists }
            - feature: kernel.loadedmodule
              matchExpressions:
                kvm: { op: Exists }
                kvm_intel: { op: Exists }
        - matchFeatures:
            - feature: cpu.cpuid
              matchExpressions:
                SSE42: { op: Exists }
                SVM: { op: Exists }
            - feature: kernel.loadedmodule
              matchExpressions:
                kvm: { op: Exists }
                kvm_amd: { op: Exists }
    - name: "amd.sev-snp"
      labels:
        amd.feature.node.kubernetes.io/snp: "true"
      extendedResources:
        sev-snp.amd.com/esids: "@cpu.security.sev.encrypted_state_ids"
      matchFeatures:
        - feature: cpu.cpuid
          matchExpressions:
            SVM: { op: Exists }
        - feature: cpu.security
          matchExpressions:
            sev.snp.enabled: { op: Exists }

    - name: "intel.sgx"
      labels:
        intel.feature.node.kubernetes.io/sgx: "true"
      extendedResources:
        sgx.intel.com/epc: "@cpu.security.sgx.epc"
      matchFeatures:
        - feature: cpu.cpuid
          matchExpressions:
            SGX: { op: Exists }
            SGXLC: { op: Exists }
        - feature: cpu.security
          matchExpressions:
            sgx.enabled: { op: IsTrue }
        - feature: kernel.config
          matchExpressions:
            X86_SGX: { op: Exists }

    - name: "intel.tdx"
      labels:
        intel.feature.node.kubernetes.io/tdx: "true"
      extendedResources:
        tdx.intel.com/keys: "@cpu.security.tdx.total_keys"
      matchFeatures:
        - feature: cpu.cpuid
          matchExpressions:
            VMX: { op: Exists }
        - feature: cpu.security
          matchExpressions:
            tdx.enabled: { op: Exists }
  1. Create the NodeFeatureRule CR by running the following command:

    $ oc create -f my-nodefeaturerule.yaml
    Note

    A relabeling delay of up to 1 minute might occur.

3.2. Deploying Intel TDX remote attestation

Set up the Intel® remote attestation infrastructure to enable quote generation and attestation for Intel® Trust Domain Extensions (TDX) pod virtual machines. This infrastructure includes an in-cluster Provisioning Certificate Caching Service (PCCS), automatic per-node Provisioning Certification Key (PCK) Cert ID Retrieval Tool based platform (re-)registration, and a per-node Quote Generation Service (QGS).

Note

The system does not back up the PCCS database automatically. Cluster administrators must implement a manual backup strategy for the database file located at /var/cache/pccs/ on the deployment node, typically a control plane node. If you do not have a valid backup, you must trigger an SGX Factory Reset in the BIOS to re-provision the required platform manifests.

Prerequisites

  • You must deploy the Intel® remote attestation infrastructure to enable quote generation for Intel® Trust Domain Extensions (TDX) pod virtual machines.
  • You have installed the Intel® device plugins Operator and created an instance of the Intel® Software Guard Extensions device plugin. For details, see Installing from the software catalog by using the web console in the OpenShift Container Platform documentation.
  • The node on which you deploy PCCS has Internet access.

Procedure

  1. Configure the remote attestation project:

    1. Create the intel-dcap namespace by running the following command:

      $ oc create namespace intel-dcap
    2. Switch to the intel-dcap project by running the following command:

      $ oc project intel-dcap
    3. Create dedicated service accounts for PCCS and QGS by running the following commands:

      $ oc create serviceaccount pccs-sa -n intel-dcap
      $ oc create serviceaccount qgs-sa -n intel-dcap
    4. Grant the privileged Security Context Constraint to the service accounts by running the following commands:

      $ oc adm policy add-scc-to-user privileged -z pccs-sa -n intel-dcap
      $ oc adm policy add-scc-to-user privileged -z qgs-sa -n intel-dcap
  2. Switch to the default project by running the following command:

    $ oc project default
  3. Set the PCCS variables by running the following commands:

    $ export PCCS_API_KEY="<API_KEY_VALUE>"

    To obtain the API key for the Intel® Software Guard Extensions and Intel® TDX Provisioning Certification Service, navigate to the Intel Trusted Services API portal, sign in, and subscribe to the Provisioning Certification Service. The API key is displayed on the Manage Subscriptions page.

    $ export PCCS_USER_TOKEN="${PCCS_USER_TOKEN:-mytoken}"

    For details about PCCS tokens, see the Design Guide for Intel® SGX Provisioning Certificate Caching Service (Intel® SGX PCCS).

    $ export PCCS_ADMIN_TOKEN="${PCCS_ADMIN_TOKEN:-mytoken}"
    $ export PCCS_NODE=$(oc get nodes \
      -l 'node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=' \
      -o jsonpath='{.items[0].metadata.name}')
  4. Set the cluster proxy variable by running the appropriate command:

    $ export CLUSTER_HTTPS_PROXY="$(oc get proxy/cluster \
      -o jsonpath={.spec.httpsProxy})"
    $ export CLUSTER_NO_PROXY="$(oc get proxy/cluster \
      -o jsonpath={.spec.noProxy})"
  5. Create the PCCS secrets:

    1. Set the PCCS secrets variables by running the following commands:

      $ export PCCS_USER_TOKEN_HASH=$(echo -n "$PCCS_USER_TOKEN" | sha512sum | tr -d '[:space:]-')
      $ export PCCS_ADMIN_TOKEN_HASH=$(echo -n "$PCCS_ADMIN_TOKEN" | sha512sum | tr -d '[:space:]-')
      $ export PCCS_PEM_CERT_PATH=$(mktemp -d)
      Note

      This directory is automatically deleted at reboot. To re-use the PCCS certificate and key, you must create a persistent directory.

    2. Generate an RSA key pair and output the private key as a PCCS certificate by running the following command:

      $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 \
        -keyout $PCCS_PEM_CERT_PATH/private.pem \
        -out $PCCS_PEM_CERT_PATH/certificate.pem \
        -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com"
    3. Set the PCCS certificate variables by running the following commands:

      $ export PCCS_PEM=$(cat "$PCCS_PEM_CERT_PATH"/private.pem | base64 | tr -d '\n')
      $ export PCCS_CERT=$(cat "$PCCS_PEM_CERT_PATH"/certificate.pem | base64 | tr -d '\n')
    4. Create the PCCS secrets by running the following command:

      $ oc create secret generic pccs-secrets \
          --namespace intel-dcap \
          --from-literal=PCCS_API_KEY="$PCCS_API_KEY" \
          --from-literal=PCCS_USER_TOKEN_HASH="$PCCS_USER_TOKEN_HASH" \
          --from-literal=USER_TOKEN="$PCCS_USER_TOKEN" \
          --from-literal=PCCS_ADMIN_TOKEN_HASH="$PCCS_ADMIN_TOKEN_HASH"
  6. Create the PCCS by running the following command:

    $ oc apply -f <(curl -sSf https://raw.githubusercontent.com/openshift/sandboxed-containers-operator/refs/tags/v1.12.0/scripts/install-helpers/baremetal-coco/intel-dcap/pccs.yaml.in|envsubst)
  7. Configure the PCCS deployment to use the dedicated service account by running the following command:

    $ oc set serviceaccount deployment/pccs pccs-sa -n intel-dcap
  8. Create the QGS by running the following command:

    $ oc apply -f https://raw.githubusercontent.com/openshift/sandboxed-containers-operator/refs/tags/v1.12.0/scripts/install-helpers/baremetal-coco/intel-dcap/qgs.yaml
  9. Configure the QGS DaemonSet to use the dedicated service account by running the following command:

    $ oc set serviceaccount daemonset/tdx-qgs qgs-sa -n intel-dcap

3.3. Enabling confidential containers

You enable confidential containers and specify the deployment mode by creating an osc-feature-gates config map.

The deployment mode determines how the Operator installs and configures the Kata runtime. This flexibility allows the Operator to work consistently in clusters with or without the Machine Config Operator (MCO).

MachineConfig
For clusters that use the Machine Config Operator (MCO). If the deploymentMode key is missing in the config map, the Operator defaults to the MachineConfig for backward compatibility.
DaemonSet
For clusters without the MCO. The Operator uses a DaemonSet to install kata-containers RPMs and manage CRI-O configuration by using host drop-in files. Installation progress is tracked through node labels (for example, installing, installed).
DaemonSetFallback
Enables conditional deployment based on the cluster environment. When set, the operator checks for the presence of the MCO. It uses DaemonSet if the MachineConfig add-on is unavailable and defaults to MachineConfig otherwise.

Procedure

  1. Create a my-feature-gate.yaml manifest file:

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: osc-feature-gates
      namespace: openshift-sandboxed-containers-operator
    data:
      confidential: "true"
      deploymentMode: <deployment_mode>
    <deployment_mode>
    Specify the deployment mode.
  2. Create the config map by running the following command:

    $ oc create -f my-feature-gate.yaml

You can initialize a pod with workload-specific data at runtime by creating and applying initdata.

This approach enhances security by reducing the exposure of confidential information and improves flexibility by eliminating custom image builds. For example, initdata can include three configuration settings:

  • An X.509 certificate for secure communication.
  • A cryptographic key for authentication.
  • An optional Kata Agent policy.rego file to enforce runtime behavior when overriding the default Kata Agent policy.

The initdata content configures the following components:

  • Attestation Agent (AA), which verifies the trustworthiness of the pod by sending evidence for attestation.
  • Confidential Data Hub (CDH), which manages secrets and secure data access within the pod VM.
  • Kata Agent, which enforces runtime policies and manages the lifecycle of the containers inside the pod VM.

You create an initdata.toml file and convert it to a gzip-format Base64-encoded string.

You apply initdata to a confidential containers pod by adding an annotation to the pod manifest.

Configure confidential containers to use NVIDIA graphics processing units (GPUs). By configuring the required Operators and custom resources, you can provision both regular and confidential GPUs for your sandboxed workloads.

Use NVIDIA graphics processing units (GPUs) as a trusted execution environment (TEE) to provide hardware-based isolation for your confidential workloads. Leveraging NVIDIA GPUs within a TEE protects data and code in memory from unauthorized access or tampering, even from privileged users or the host operating system.

When you deploy confidential containers on bare-metal servers with NVIDIA GPU support, you must manually configure the MachineConfig with the required kernel arguments for GPU integration. After configuring the MachineConfig, verify that the kernel arguments are correctly applied to the machine config pool where Kata containers and GPU support are configured to run.

3.5.2. Create a MachineConfig for NVIDIA GPUs

Enable Input-Output Memory Management Unit (IOMMU) kernel parameters on your worker nodes. This configuration helps you support GPU pass-through for your sandboxed containers.

Prerequisites

  • You have access to the cluster as a user with the cluster-admin role.

Procedure

  1. Create a gpu-machine-config.yaml manifest file according to the following example:

    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    metadata:
      labels:
        machineconfiguration.openshift.io/role: worker
      name: 100-iommu-kernel-args
    spec:
      config:
        ignition:
          version: 3.2.0
      kernelArguments:
      - amd_iommu=on
      - intel_iommu=on
    Note

    If using Single Node OpenShift (SNO), replace worker with master in the machineconfiguration.openshift.io/role label.

    The nodes will reboot after applying this configuration.

  2. Create the config map by running the following command:

    $ oc create -f gpu-machine-config.yaml

Verification

  • Verify the kernel parameters are set by running the following commands:

    $ oc debug node/<node_name>
    $ cat /proc/cmdline | grep iommu

3.5.3. Install the Node Feature Discovery Operator

Install the Node Feature Discovery (NFD) Operator to detect hardware features and system configurations on your cluster nodes. This tool enables automatic labeling based on the detected features

Prerequisites

  • You have installed OpenShift sandboxed containers Operator.
  • You have access to the cluster as a user with the cluster-admin role.

Procedure

  1. Install the Node Feature Discovery (NFD) Operator by following the OpenShift Container Platform documentation.

Verification

  • Verify the NFD Operator is active by running the following command:

    $ oc get pods -n openshift-nfd

    Example output

    NAME                                      READY   STATUS    RESTARTS   AGE
    nfd-controller-manager-5d8d9d9f8b-abcde   2/2     Running   0          2m

3.5.4. Create a node feature rule for NVIDIA GPUs

Create a NodeFeatureRule custom resource to match NVIDIA kernel modules on your cluster. This custom resource enables the automatic labeling of nodes with compatible NVIDIA graphics processing units.

Prerequisites

Procedure

  1. Create a NodeFeatureRule custom resource to match NVIDIA kernel modules by running the following command:

    apiVersion: nfd.openshift.io/v1alpha1
    kind: NodeFeatureRule
    metadata:
      name: nvidia-kernel-modules
    spec:
      rules:
        - name: kernel-module-gdrdrv
          labels:
            nvidia.com/gdrcopy.capable: "true"
          matchFeatures:
            - feature: kernel.loadedmodule
              matchExpressions:
                gdrdrv:
                  op: Exists
        - name: kernel-module-nvidia_fs
          labels:
            nvidia.com/gds.capable: "true"
          matchFeatures:
            - feature: kernel.loadedmodule
              matchExpressions:
                nvidia_fs:
                  op: Exists
        - name: kernel-module-nvidia_peermem
          labels:
            nvidia.com/peermem.capable: "true"
          matchFeatures:
            - feature: kernel.loadedmodule
              matchExpressions:
                nvidia_peermem:
                  op: Exists
  2. Create the NodeFeatureRule CR by running the following command:

    $ oc create -f my-nfd-gpu.yaml

Verification

  • Verify that the labels are applied to nodes by running the following command:

    $ oc get nodes -o json | jq '.items[].metadata.labels | with_entries(select(.key | startswith("nvidia.com")))'

3.5.5. Install the NVIDIA GPU Operator

You must install the NVIDIA GPU Operator to manage GPU resources in your cluster.

Prerequisites

  • You have created the NodeFeatureRule custom resource for NVIDIA GPUs.
  • You have access to the cluster as a user with the cluster-admin role.

Procedure

  1. Install the NVIDIA GPU Operator version 26.3.0. For detailed installation instructions, see the NVIDIA GPU Operator documentation.
  2. Verify that the required labels are present on your worker nodes by running the following command:

    $ oc get nodes -o custom-columns=NAME:.metadata.name,LABELS:.metadata.labels

    Ensure that nodes have the appropriate NVIDIA GPU labels. If labels are missing, add them manually by running the following command:

    $ oc label node <node_name> nvidia.com/gpu.present=true

Verification

  • Verify that the GPU Operator pods are running by running the following command:

    $ oc get pods -n nvidia-gpu-operator
    NAME                                       READY   STATUS    RESTARTS   AGE
    gpu-operator-1234567890-abcde              1/1     Running   0          10m

3.5.6. Create the ClusterPolicy CR for NVIDIA GPUs

Create a ClusterPolicy custom resource to configure the NVIDIA GPU Operator. This policy helps you correctly set up and manage the operator for use with OpenShift sandboxed containers.

Prerequisites

  • You have installed the NVIDIA GPU Operator.
  • You have access to the cluster as a user with the cluster-admin role.

Procedure

  1. Create a my-cluster-policy-gpu.yaml manifest file according to the following example:

    apiVersion: nvidia.com/v1
    kind: ClusterPolicy
    metadata:
      name: gpu-cluster-policy
    spec:
      ccManager:
        defaultMode: "on"
        enabled: true
      cdi:
        default: false
        enabled: true
        nriPluginEnabled: false
      daemonsets:
        rollingUpdate:
          maxUnavailable: '1'
        updateStrategy: RollingUpdate
      dcgm:
        enabled: false
      dcgmExporter:
        config:
          name: ''
        enabled: false
        serviceMonitor:
          enabled: true
      devicePlugin:
        config:
          default: ''
          name: ''
        enabled: false
        mps:
          root: /run/nvidia/mps
      driver:
        certConfig:
          name: ''
        enabled: false
        kernelModuleConfig:
          name: ''
        kernelModuleType: auto
        licensingConfig:
          configMapName: ''
          nlsEnabled: true
        repoConfig:
          configMapName: ''
        upgradePolicy:
          autoUpgrade: true
          drain:
            deleteEmptyDir: false
            enable: false
            force: false
            timeoutSeconds: 300
          maxParallelUpgrades: 1
          maxUnavailable: 25%
          podDeletion:
            deleteEmptyDir: false
            force: false
            timeoutSeconds: 300
          waitForCompletion:
            timeoutSeconds: 0
        useNvidiaDriverCRD: false
        useOpenKernelModules: false
        virtualTopology:
          config: ''
      gdrcopy:
        enabled: false
      gds:
        enabled: false
      gfd:
        enabled: true
      kataManager:
        enabled: false
      mig:
        strategy: single
      migManager:
        enabled: false
      nodeStatusExporter:
        enabled: true
      operator:
        defaultRuntime: crio
        initContainer: {}
        runtimeClass: nvidia
        use_ocp_driver_toolkit: true
      kataSandboxDevicePlugin:
        enabled: true
        env:
          - name: P_GPU_ALIAS
            value: pgpu
          - name: NVSWITCH_ALIAS
            value: nvswitch
      sandboxWorkloads:
        defaultWorkload: vm-passthrough
        enabled: true
        mode: kata
      toolkit:
        enabled: false
        installDir: /usr/local/nvidia
      validator:
        plugin:
          env:
            - name: WITH_WORKLOAD
              value: 'false'
      vfioManager:
        enabled: true
        env:
          - name: BIND_NVSWITCHES
            value: 'true'
      vgpuDeviceManager:
        enabled: false
      vgpuManager:
        enabled: false
  2. Create the ClusterPolicy CR by running the following command:

    $ oc create -f my-cluster-policy-gpu.yaml

Verification

  1. Verify the GPU Operator setup by running the following command:

    $ oc get pods -n nvidia-gpu-operator
    NAME                                                READY   STATUS    RESTARTS      AGE
    gpu-operator-cb99f5757-djl7k                        1/1     Running   2             16h
    nvidia-cc-manager-hjd6t                             1/1     Running   5 (42m ago)   16h
    nvidia-kata-sandbox-device-plugin-daemonset-wn6bc   1/1     Running   2             16h
    nvidia-sandbox-validator-7cvx5                      1/1     Running   0             70m
    nvidia-vfio-manager-zsmqn                           1/1     Running   2             16h
  2. Verify the CC Manager DaemonSet by running the following command:

    $ oc get daemonset -n nvidia-gpu-operator | grep cc-manager
    nvidia-cc-manager     1         1         1       1            1           nvidia.com/gpu.deploy.cc-manager=true   7m43s
  3. Create a sample pod to test the GPU allocation.

    Note

    You must create the KataConfig custom resource before creating the GPU sample pod. See Creating the KataConfig custom resource.

    For confidential GPUs, create the following pod:

    apiVersion: v1
    kind: Pod
    metadata:
      name: sample-gpu-pod
      annotations:
        io.katacontainers.config.hypervisor.default_memory: "32768"
        io.katacontainers.config.hypervisor.cc_init_data: "H4sIAAAAAAAAA6WUS2/TQBCA7/4Vli85xQ1IoKpSDyUJUIkQy07JoYqiyXpir7IPszsONb++YyIQINauxMGHnfk875kzOi+tiW/jZJa+SmdJBKqyTlKte5mv4fWbt0n0WALBLkoaq6ToUoeVTVg/mUyiBsQJKoz5M7S/AFFU4hFaRfFdWd7l2WeUVX2wzuf4tUVP8c1tTK7F37HiGzQh9VxZjwWV0gQJ23TvpcKg3iEQzq0hkAbdMFaAKQ/2KQQtWOhsN0Itn1Bkzgr0waw/IK2QnBRDxHq9Wp65tEGkly6Q81JBM5+kp3tD6I7A8QxRuW0pTKxQf7TUqLZ613Fuh2C510ZxmefZA/8RYjJo/XhHslape83DFQJyhLIg7lvQUY7ansc9XbCCQOEX6Ujaoy9qcLiyraFgSXL0iGXOs2D1As8DWKvHYyiQLv3kKdxIHUyauezHpgUBWRlQI/PHuToaj6mnNg6ENNUAQ/4FlmwzYmhD3bbfci+/B5N/aMqX7PIFWzY1anSghtt4gX/tyDA2vCRbkDRS+C0fWPz30PYnNQFIyWr188Q+kj2h2QtrjrLyu7/e6enAstap/mDXRM3N1RWLph7dWQpM2awnxKltuAxk3dR3/NY317NrPve9/SgRZf2nx9NB7CIDGnujQuz5nfyXj2f3zZjpaQYAAA=="
    spec:
      runtimeClassName: kata-cc-nvidia-gpu
      restartPolicy: OnFailure
      containers:
        - name: gpu-cc-verifier
          image: quay.io/openshift_sandboxed_containers/gpu-verifier:ubi9
          imagePullPolicy: IfNotPresent
          command: ["/bin/bash"]
          args:
            - -c
            - |
              /opt/cuda-samples/Samples/0_Introduction/vectorAdd/build/vectorAdd
              sleep 36000
          resources:
            limits:
              nvidia.com/pgpu: 1
          securityContext:
            privileged: false
    Note

    The io.katacontainers.config.hypervisor.cc_init_data annotation includes a permissive kata-agent policy for verification purposes. The "exec" and "log" APIs use an embedded kata-agent policy that disables them. This configuration does not include a Key Broker Service (KBS) URL, which prevents issues in customer environments where the KBS URL might not align to the actual deployment.

  4. Verify the sample pod is running successfully by running the following command:

    $ oc get pods
    NAME               READY   STATUS    RESTARTS   AGE
    sample-gpu-pod     1/1     Running   0          2m
  5. Check the pod logs to verify GPU functionality by running the following command:

    $ oc logs sample-gpu-pod
    [Vector addition of 50000 elements]
    Copy input data from the host memory to the CUDA device
    CUDA kernel launch with 196 blocks of 256 threads
    Copy output data from the CUDA device to the host memory
    Test PASSED
    Done

Apply specific labels to your worker nodes so you can use NVIDIA GPUs with OpenShift sandboxed containers. The NVIDIA GPU Operator typically adds these labels automatically when it detects compatible hardware configured for VFIO passthrough mode.

The required labels depend on whether you are deploying confidential GPUs.

Labels for confidential GPUs

For confidential GPU workloads using the kata-cc-nvidia-gpu runtime class, nodes must have the base Kata and GPU labels, plus additional labels for confidential computing and the Trusted Execution Environment (TEE). Nodes must have the following labels:

  • Base Kata label:

    • feature.node.kubernetes.io/runtime.kata: "true"
  • Base GPU labels:

    • nvidia.com/gpu.present: "true"
    • nvidia.com/gpu.deploy.vfio-manager: "true"
    • nvidia.com/gpu.deploy.kata-sandbox-device-plugin: "true"
  • Confidential computing GPU labels:

    • nvidia.com/cc.mode.state: "on"
    • nvidia.com/cc.ready.state: "true"
    • nvidia.com/gpu.deploy.cc-manager: "true"
  • TEE label (one of the following):

    • intel.feature.node.kubernetes.io/tdx: "true"
    • amd.feature.node.kubernetes.io/snp: "true"

3.6. Create the KataConfig custom resource

You must create the KataConfig custom resource (CR) to install kata-cc as a runtime class on your worker nodes.

Note

If you want to configure NVIDIA GPUs for confidential containers, complete the GPU configuration steps before creating the KataConfig CR. For details, see Configure confidential containers for NVIDIA GPUs.

Prerequisites

  • Creating the KataConfig CR automatically reboots the worker nodes. The reboot can take from 10 to more than 60 minutes depending on your deployment size, hardware type, and other factors.

Procedure

  1. Create an example-kataconfig.yaml manifest file according to the following example:

    apiVersion: kataconfiguration.openshift.io/v1
    kind: KataConfig
    metadata:
      name: example-kataconfig
    spec:
      enablePeerPods: false
      checkNodeEligibility: true
      logLevel: info
    #  kataConfigPoolSelector:
    #    matchLabels:
    #      <label_key>: '<label_value>'
    <label_key>: '<label_value>'
    Optional: If you have applied node labels to install kata-cc on specific nodes, specify the key and value, for example, kata-cc: 'true'.
  2. Create the KataConfig CR by running the following command:

    $ oc create -f example-kataconfig.yaml

    The new KataConfig CR is created and installs kata-cc as a runtime class on the worker nodes.

    Wait for the kata-cc installation to complete and the worker nodes to reboot before verifying the installation.

  3. Monitor the installation progress by running the following command:

    $ watch "oc describe kataconfig | sed -n /^Status:/,/^Events/p"

    When the status of all workers under kataNodes is installed and the condition InProgress is False without specifying a reason, the kata-cc is installed on the cluster.

  4. Verify the runtime classes by running the following command:

    $ oc get runtimeclass

    +

NAME           HANDLER             AGE
kata            kata                 34m
{runtime}         {runtime-handler}            152m

+ You can also see the default kata runtime class in addition to kata-cc.

3.6.1. The checkNodeEligibility parameter

To manage node selection for your workloads, configure the checkNodeEligibility parameter in the KataConfig resource. This determines if runtime classes are created based on hardware labels or unconditionally. From 1.12.0, this applies to all standard and confidential container (CC) runtimes.

When checkNodeEligibility is set to true

The Operator performs the following actions:

  • Node eligibility verification: The Operator verifies that nodes have the required hardware capabilities by using node labels before installing the Kata runtime.
  • Conditional runtime class creation: The Operator creates runtime classes only if nodes with the required labels exist in the cluster:

    • Standard runtime classes: The kata or kata-nvidia-gpu runtime classes are created only if nodes with the required base and GPU labels exist.
    • Confidential container runtime classes: The kata-cc or kata-cc-nvidia-gpu runtime classes are created only if nodes with the required Trusted Execution Environment (TEE) labels (such as Intel® Trust Domain Extensions (TDX) or AMD SEV-SNP) and the corresponding confidential containers and GPU labels exist.
  • Dynamic runtime class management: If no nodes match the required labels, the corresponding runtime class is not created. This prevents workload scheduling failures by ensuring users cannot select a runtime that the cluster cannot support.
When checkNodeEligibility is set to false (default)

The Operator performs the following actions:

  • Unconditional creation for standard runtimes: The Operator always creates the kata and kata-nvidia-gpu runtime classes, regardless of whether nodes currently have the required hardware labels.
  • Identification-based creation for CC runtimes: For the kata-cc and kata-cc-nvidia-gpu runtime classes, the Operator still depends on the TEE label for identification, but it does not verify the base or GPU labels during the installation phase.
  • Manual scheduling: The Operator skips the detailed node label check during installation. The cluster will only schedule pods using these runtime classes if a node eventually matches the nodeSelector defined in the runtime class.

Additional resources

3.7. Create initdata

You create initdata to securely initialize a pod with sensitive or workload-specific data at runtime, thus avoiding the need to embed this data in a virtual machine image. This approach provides additional security by reducing the risk of exposure of confidential information and eliminates the need for custom image builds.

Prerequisites

  • You have installed the Node Feature Discovery (NFD) Operator. For more information, see Node Feature Discovery Operator in the OpenShift Container Platform documentation.
  • You have deleted the kbs_cert setting if you configure insecure_http = true in the kbs-config config map for Red Hat build of Trustee.

Procedure

  1. Obtain the Red Hat build of Trustee URL by running the following command:

    $ TRUSTEE_URL=$(oc get route kbs-service \
      -n trustee-operator-system -o jsonpath='{.spec.host}') \
      && echo $TRUSTEE_URL
  2. Create the initdata.toml file:

    algorithm = <algorithm>
    version = "0.1.0"
    [data]
    "aa.toml" = '''
    [token_configs]
    [token_configs.coco_as]
    
    url = '<trustee_url>'
    
    [token_configs.kbs]
    url = '<trustee_url>'
    '''
    "cdh.toml" = '''
    socket = 'unix:///run/confidential-containers/cdh.sock'
    credentials = []
    
    [kbc]
    name = 'cc_kbc'
    url = '<trustee_url>'
    kbs_cert = """
    -----BEGIN CERTIFICATE-----
    <kbs_certificate>
    -----END CERTIFICATE-----
    """
    [image]
    image_security_policy_uri = 'kbs:///default/<secret-policy-name>/<key>
    '''
    
    "policy.rego" = '''
    package agent_policy
    
    default AddARPNeighborsRequest := true
    default AddSwapRequest := true
    default CloseStdinRequest := true
    default CopyFileRequest := true
    default CreateContainerRequest := true
    default CreateSandboxRequest := true
    default DestroySandboxRequest := true
    default GetMetricsRequest := true
    default GetOOMEventRequest := true
    default GuestDetailsRequest := true
    default ListInterfacesRequest := true
    default ListRoutesRequest := true
    default MemHotplugByProbeRequest := true
    default OnlineCPUMemRequest := true
    default PauseContainerRequest := true
    default PullImageRequest := true
    default ReadStreamRequest := false
    default RemoveContainerRequest := true
    default RemoveStaleVirtiofsShareMountsRequest := true
    default ReseedRandomDevRequest := true
    default ResumeContainerRequest := true
    default SetGuestDateTimeRequest := true
    default SignalProcessRequest := true
    default StartContainerRequest := true
    default StartTracingRequest := true
    default StatsContainerRequest := true
    default StopTracingRequest := true
    default TtyWinResizeRequest := true
    default UpdateContainerRequest := true
    default UpdateEphemeralMountsRequest := true
    default UpdateInterfaceRequest := true
    default UpdateRoutesRequest := true
    default WaitProcessRequest := true
    default ExecProcessRequest := false
    default SetPolicyRequest := false
    default WriteStreamRequest := false
    
    default ExecProcessRequest := false
    '''
    algorithm
    Specify sha256, sha384, or sha512.
    url
    Specify Red Hat build of Trustee
    <kbs_certificate>
    Specify the Base64-encoded TLS certificate for the attestation agent.
    kbs_cert
    Delete the kbs_cert setting if you configure insecure_http = true in the kbs-config config map for Red Hat build of Trustee.
    image_security_policy_uri
    Optional, only if you enabled the container image signature verification policy. Replace <secret-policy-name> and <key> with the secret name and key, respectively specified in Creating the KbsConfig custom resource.
  3. Convert the initdata.toml file to a gzipped, Base64-encoded string in a text file by running the following command:

    $ cat initdata.toml | gzip | base64 -w0 > initdata.txt

    Record this string to use in the pod manifest.

  4. Calculate the hash of an initdata.toml file and assign its value to the hash variable by running the following command:

    $ hash=$(<algorithm> initdata.toml | cut -d' ' -f1)
  5. Assign 32 bytes of 0s to the initial_pcr variable by running the following command:

    $ initial_pcr=0000000000000000000000000000000000000000000000000000000000000000
  6. Calculate the SHA-256 hash of hash and initial_pcr and assign its value to the PCR8_HASH variable by running the following command:

    $ PCR8_HASH=$(echo -n "$initial_pcr$hash" | xxd -r -p | sha256sum | cut -d' ' -f1) && echo $PCR8_HASH

    Record the PCR8_HASH value for the RVPS config map.

3.8. Applying initdata to a pod

Prerequisite

  • You have created an initdata string.

Procedure

  1. Add the initdata string to the pod manifest and save the file as my-pod.yaml:

    apiVersion: v1
    kind: Pod
    metadata:
      name: ocp-cc-pod
      labels:
        app: ocp-cc-pod
      annotations:
        io.katacontainers.config.hypervisor.cc_init_data: <initdata_string>
    spec:
      runtimeClassName: kata-cc
      containers:
      - name: <container_name>
        image: registry.access.redhat.com/ubi9/ubi:latest
        command:
        - sleep
        - "36000"
        securityContext:
          privileged: false
          seccompProfile:
            type: RuntimeDefault

    where

    <initdata_string>
    Specify the gzipped, Base64-encoded initdata value in a pod annotation to override the global INITDATA setting in the peer pods config map.
    <container_name>
    Specify a container name.
  2. Create the pod by running the following command:

    $ oc create -f my-pod.yaml

3.9. Verifying attestation

You can verify the attestation process by creating a test pod to retrieve a specific resource from Red Hat build of Trustee.

Important

This procedure is an example to verify that attestation is working. Do not write sensitive data to standard I/O, because the data can be captured by using a memory dump. Only data written to memory is encrypted.

Procedure

  1. Create a test-pod.yaml manifest file:

    apiVersion: v1
    kind: Pod
    metadata:
      name: ocp-cc-pod
      labels:
        app: ocp-cc-pod
      annotations:
        io.katacontainers.config.hypervisor.cc_init_data: "<initdata_string>"
    spec:
      runtimeClassName: kata-cc
      containers:
        - name: skr-openshift
          image: registry.access.redhat.com/ubi9/ubi:latest
          command:
            - sleep
            - "36000"
          securityContext:
            privileged: false
            seccompProfile:
              type: RuntimeDefault
    metadata:
      name: coco-test-pod
      labels:
        app: coco-test-pod
      annotations:
        io.katacontainers.config.hypervisor.cc_init_data: "<initdata_string>"
    spec:
      runtimeClassName: kata-cc
      containers:
        - name: test-container
          image: registry.access.redhat.com/ubi9/ubi:9.3
          command:
            - sleep
            - "36000"
          securityContext:
            privileged: false
            seccompProfile:
              type: RuntimeDefault

    where:

    io.katacontainers.config.hypervisor.cc_init_data
    Optional: Specifies initdata in a pod annotation, which overrides the global INITDATA setting in the peer pods config map.
  2. Create the pod by running the following command:

    $ oc create -f test-pod.yaml
  3. Log in to the pod by running the following command:

    $ oc exec -it ocp-cc-pod -- bash
  4. Fetch the Red Hat build of Trustee resource by running the following command:

    $ curl http://127.0.0.1:8006/cdh/resource/default/attestation-status/status

    Example output

    success #/

3.10. Configuring your workload

You configure your workload for confidential containers by setting kata-cc as the runtime class for the following pod-templated objects:

  • Pod objects
  • ReplicaSet objects
  • ReplicationController objects
  • StatefulSet objects
  • Deployment objects
  • DeploymentConfig objects
Important

Do not deploy workloads in an Operator namespace. Create a dedicated namespace for these resources.

Prerequisites

  • You have created the KataConfig custom resource (CR).

Procedure

  1. Add spec.runtimeClassName: kata-cc to the manifest of each pod-templated workload object as in the following example:

    apiVersion: v1
    kind: <object>
    # ...
    spec:
      runtimeClassName: kata-cc
    # ...
  2. Apply the changes to the workload object by running the following command:

    $ oc apply -f <object.yaml>

    OpenShift Container Platform creates the workload object and begins scheduling it.

Verification

  • Inspect the spec.runtimeClassName field of a pod-templated object. If the value is kata-cc, then the workload is running on confidential containers.

3.10.1. Encrypt the block volumes

You must encrypt volumes inside the TEE to ensure data stays private. Rather than relying on host-level CSI drivers, you attach raw blocks, use an init container for Linux Unified Key Setup (LUKS) formatting, and mount to your app by using shared namespaces and hooks. This keeps data secure in use, in memory, and at rest.

Prerequisites

  • You have installed the Container Storage Interface (CSI) driver configured for raw block volumes. For more information, see Understanding persistent storage.
  • You have installed OpenShift sandboxed containers on a bare-metal server.
  • You have configured an attestation service, such as Red Hat build of Trustee, to provide secrets like the encryption passphrase.

Procedure

  1. Create a storage-encrypted.yaml manifest file for the PersistentVolumeClaim object with the volumeMode parameter set to Block:

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: storage-encrypted
    spec:
      accessModes:
        - ReadWriteOnce
      volumeMode: Block
      resources:
        requests:
          storage: <size>
  2. Create the PersistentVolumeClaim object by running the following command:

    $ oc create -f storage-encrypted.yaml
  3. Create an encrypted-pod.yaml manifest file with the complete pod specification:

    apiVersion: v1
    kind: Pod
    metadata:
      annotations:
        io.katacontainers.config.hypervisor.cc_init_data: <init_data>
      name: storage-encrypted
      labels:
        app: storage-encrypted
    spec:
      runtimeClassName: kata-cc
      shareProcessNamespace: true
      initContainers:
      - name: format-disk
        image: registry.redhat.io/openshift-sandboxed-containers/osc-storage-helper:1.12.0
        command: ["/usr/local/bin/luks-helper", "format-disk"]
        securityContext:
          privileged: true
        restartPolicy: Always
        env:
          - name: PASS
            valueFrom:
              secretKeyRef:
                name: <my_sealed_secret>
                key: <secret_key>
        volumeMounts:
          - name: storage-ipc
            mountPath: /dev/shm
        volumeDevices:
          - name: luks-block
            devicePath: /dev/block-device
      - name: check-ready
        image: registry.redhat.io/openshift-sandboxed-containers/osc-storage-helper:1.12.0
        command: ["/usr/local/bin/luks-helper", "wait-ready"]
        securityContext:
          privileged: true
        volumeMounts:
          - name: storage-ipc
            mountPath: /dev/shm
      containers:
      - name: <container_name>
        image: <image_name>
        ports:
          - containerPort: 8888
        env:
          - name: DATA_DIR
            value: <mount_point>
        lifecycle:
          postStart:
            exec:
              command:
                - /bin/sh
                - -c
                - |
                  PID=$(cat /dev/shm/luks-helper.pid)
                  chmod ug+w "$(dirname "$DATA_DIR")"
                  ln -sfn "/proc/$PID/root/mnt/storage" "$DATA_DIR"
        securityContext:
          privileged: true
        volumeMounts:
          - name: storage-ipc
            mountPath: /dev/shm
      volumes:
      - name: luks-block
        persistentVolumeClaim:
          claimName: storage-encrypted
      - name: storage-ipc
        emptyDir:
          medium: Memory

    where:

    <init_data>
    Specifies the initdata for the runtime configuration.
    <my_sealed_secret>
    Specifies the name of the sealed secret that contains the LUKS encryption passphrase.
    <secret_key>
    Specifies the key within the sealed secret that contains the encryption passphrase.
    <container_name>
    Specifies the container name for your application container.
    <image_name>
    Specifies the image name.
    <mount_point>
    Specifies the mount point for encrypted storage inside your application container.
  4. Create the pod by running the following command:

    $ oc create -f encrypted-pod.yaml
Red Hat logoGithubredditYoutubeTwitter

Learn

Try, buy, & sell

Communities

About Red Hat

We deliver hardened solutions that make it easier for enterprises to work across platforms and environments, from the core datacenter to the network edge.

Making open source more inclusive

Red Hat is committed to replacing problematic language in our code, documentation, and web properties. For more details, see the Red Hat Blog.

About Red Hat Documentation

Legal Notice

Theme

© 2026 Red Hat
Back to top