This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Protected Storage

Options for confidential storage

By default, CoCo workloads execute in confidential guest memory. Files written to the workload filesystem will be stored in guest memory and protected by confidential computing.

Of course, cloud native workloads can leverage a wide variety of external storage options. Some of these might break the confidential trust model. Some can be used with adaptations to the workload (e.g. wrapping secrets before storing them). Carefully consider the trust model of any external storage volumes, services, or paradigms before attaching them to a confidential workload.

To simplify things, Confidential Containers provides some confidential storage primitives.

1 - Confidential EmptyDir

Protected ephemeral storage for workloads

When using a confidential runtime class, all emptyDir volumes will automatically be created on top of secure block devices. These confidential emptyDir volumes use LUKS2 on top of a block device provided by the host.

Confidential emptyDir can be a good fit for a workload that needs to write a lot of secret data to a scratch directory. If stored inside the guest, this data could deplete guest memory. Instead, the confidential emptyDir is backed by a block device provided by the host. The block device is encrypted inside the guest such that the host cannot access the data.

A confidential emptyDir can be added to a workload the same way a traditional emptyDir would be used.

volumeMounts:
      - name: scratch-volume 
        mountPath: /scratch-directory
  volumes:
  - name: scratch-volume
    emptyDir:
      sizeLimit: 64Gi

On the host, this volume will be backed by a sparse file. Data blocks are allocated as the volume is used, but creating the filesystem, encryption, and integrity metadata consumes some physical host storage immediately.

Confidential emptyDir volumes are ephemeral. They are removed when the pod is torn down.

The LUKS2 header for the volume is stored in guest memory and is not accessible to the host.

If you want to use an emptyDir that isn’t backed by a LUKS volume, set the emptyDir medium to Memory. This will create an emptyDir that is stored in guest memory.

volumeMounts:
  - name: memory-empty-vol
    mountPath: "/tmp/cache"
volumes:
  - name: memory-empty-vol
  emptyDir:
    medium: Memory
    sizeLimit: "50M"

2 - Confidential Container Image Storage

Experimental: Use protected ephemeral storage instead of confidential guest memory for guest-pulled image layers

With guest image management, container images are pulled and unpacked inside the confidential guest. Like other files written to the workload filesystem, their layers are stored in confidential guest memory by default. Specifically, the layers are written to /run/kata-containers/image, which is backed by guest memory. Large images can therefore significantly increase a pod’s memory requirements.

This experimental storage feature moves these layers to a host-provided block device. The Kata agent asks the Confidential Data Hub (CDH) to encrypt and integrity-protect the device inside the guest using dm-crypt and dm-integrity, then mount it at /run/kata-containers/image. The host provides the storage but cannot read or modify individual data blocks without detection.

Provide a raw block PersistentVolumeClaim using a suitable storage driver and expose it to a container at the reserved device path /dev/trusted_store. The PersistentVolumeClaim must use volumeMode: Block. The Kata agent detects the reserved device path and prepares the device for image-layer storage.

spec:
  volumes:
    - name: image-storage
      persistentVolumeClaim:
        claimName: image-storage-pvc
  containers:
    - name: app
      volumeDevices:
        - name: image-storage
          devicePath: /dev/trusted_store

From the workload’s perspective, this storage is ephemeral: it is not an image cache that can be reused by later pods. The lifecycle of the underlying PersistentVolume still follows its Kubernetes reclaim policy. The feature reduces confidential guest memory usage for container image layers.

See the Kata Containers guide for the complete guest image management and block-volume setup.

Try it with local storage

For prototyping on a single node, a file on a disk-backed host filesystem can be attached as a loop device and exposed through a local PersistentVolume. This setup requires root access to the node and is not suitable for production.

Create a sparse 64 GiB backing file and attach it to an available loop device. The host filesystem must have enough free space as the file grows:

NODE_NAME="$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')"
LOOP_FILE="/var/lib/coco/image-storage.img"

sudo mkdir -p "$(dirname "${LOOP_FILE}")"
sudo truncate --size 64G "${LOOP_FILE}"
LOOP_DEVICE="$(sudo losetup --find --show "${LOOP_FILE}")"

Create a StorageClass, local PersistentVolume, and raw block PersistentVolumeClaim for that device:

cat <<EOF | kubectl apply -f -
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-image-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: image-storage-pv
spec:
  capacity:
    storage: 64Gi
  volumeMode: Block
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-image-storage
  local:
    path: ${LOOP_DEVICE}
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - ${NODE_NAME}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: image-storage-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 64Gi
  volumeMode: Block
  storageClassName: local-image-storage
EOF

The PVC can remain Pending until the pod is created because the StorageClass uses WaitForFirstConsumer. Attach the claim to an existing guest-pull workload at /dev/trusted_store.

After deleting the workload, remove the storage objects, detach the loop device, and remove the backing file:

kubectl delete pvc image-storage-pvc
kubectl delete pv image-storage-pv
kubectl delete storageclass local-image-storage
sudo losetup --detach "${LOOP_DEVICE}"
sudo rm -f "${LOOP_FILE}"