# Use persistent storage

Persistent storage in Kubernetes requires configuration based on your cloud provider of choice.
For abstraction of container storage, Kubernetes offers [volumes](https://kubernetes.io/docs/concepts/storage/volumes/),
allowing users to mount storage solutions directly into containers.
The [Container Storage Interface (CSI)](https://kubernetes-csi.github.io/docs/) is the standard interface for exposing arbitrary block and file storage systems into containers in Kubernetes.
Cloud providers offer their own CSI-based solutions for cloud storage.

### Confidential storage

Most cloud storage solutions support encryption, such as [GCE Persistent Disks (PD)](https://cloud.google.com/kubernetes-engine/docs/how-to/using-cmek).
Constellation supports the available CSI-based storage options for Kubernetes engines in Azure and GCP.
However, their encryption takes place in the storage backend and is managed by the cloud provider.
This mode of storage encryption doesn't provide confidential storage.
Using the default CSI drivers for these storage types means trusting the CSP with your persistent data.

Constellation provides CSI drivers for Azure Disk and GCE PD, offering [encryption on the node level](../architecture/keys.md#storage-encryption). They enable transparent encryption for persistent volumes without needing to trust the cloud backend. Plaintext data never leaves the confidential VM context, offering you confidential storage.

For more details see [encrypted persistent storage](../architecture/encrypted-storage.md).

## CSI Drivers

Constellation can use the following drivers which offer node level encryption and optional integrity protection.

<tabs groupId="csp">
<tabItem value="azure" label="Azure" default>

1. [Azure Disk Storage](https://github.com/edgelesssys/constellation-azuredisk-csi-driver)

   Mount Azure [Disk Storage](https://azure.microsoft.com/en-us/services/storage/disks/#overview) into your Constellation cluster. See the example below on how to install the modified Azure Disk CSI driver or check out the [repository](https://github.com/edgelesssys/constellation-azuredisk-csi-driver) for installation and more information about the Constellation-managed version of the driver. Since Azure Disks are mounted as ReadWriteOnce, they're only available to a single pod.

</tabItem>
<tabItem value="gcp" label="GCP" default>

1. [Persistent Disk](https://github.com/edgelesssys/constellation-gcp-compute-persistent-disk-csi-driver):

    Mount GCP [Persistent Disk](https://cloud.google.com/persistent-disk) block storage into your Constellation cluster.
    This includes support for [volume snapshots](https://cloud.google.com/kubernetes-engine/docs/how-to/persistent-volumes/volume-snapshots), which let you create copies of your volume at a specific point in time.
    You can use them to bring a volume back to a prior state or provision new volumes.
    Follow the examples listed below to setup the modified GCP PD CSI driver, or check out the [repository](https://github.com/edgelesssys/constellation-gcp-compute-persistent-disk-csi-driver) for information about the configuration.

</tabItem>
</tabs>

Note that in case the options above aren't a suitable solution for you, Constellation is compatible with all other CSI-based storage options. For example, you can use [Azure Files](https://docs.microsoft.com/en-us/azure/storage/files/storage-files-introduction) or [GCP Filestore](https://cloud.google.com/filestore) with Constellation out of the box. Constellation is just not providing transparent encryption on the node level for these storage types yet.

## Installation

The following installation guide gives a brief overview of using CSI-based confidential cloud storage for persistent volumes in Constellation.

<tabs groupId="csp">
<tabItem value="azure" label="Azure" default>

1. Install the CSI driver:

  ```bash
  helm install azuredisk-csi-driver charts/edgeless/latest/azuredisk-csi-driver.tgz \
      --namespace kube-system \
      --set linux.distro=fedora \
      --set controller.replicas=1
  ```

2. Create a [storage class](https://kubernetes.io/docs/concepts/storage/storage-classes/) for your driver

    A storage class configures the driver responsible for provisioning storage for persistent volume claims.
    A storage class only needs to be created once and can then be used by multiple volumes.
    The following snippet creates a simple storage class using a [Standard SSD](https://docs.microsoft.com/en-us/azure/virtual-machines/disks-types#standard-ssds) as the backing storage device when the first Pod claiming the volume is created.

    ```bash
    cat <<EOF | kubectl apply -f -
    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: encrypted-storage
      annotations:
        storageclass.kubernetes.io/is-default-class: "true"
    provisioner: azuredisk.csi.confidential.cloud
    parameters:
      skuName: StandardSSD_LRS
    reclaimPolicy: Delete
    volumeBindingMode: WaitForFirstConsumer
    EOF
    ```

:::info

By default, integrity protection is disabled for performance reasons. If you want to enable integrity protection, add `csi.storage.k8s.io/fstype: ext4-integrity` to `parameters`. Alternatively, you can use another filesystem by specifying another file system type with the suffix `-integrity`. Note that volume expansion isn't supported for integrity-protected disks.

:::

</tabItem>
<tabItem value="gcp" label="GCP" default>

1. Install the CSI driver:

   ```bash
   git clone https://github.com/edgelesssys/constellation-gcp-compute-persistent-disk-csi-driver.git
   cd constellation-gcp-compute-persistent-disk-csi-driver
   kubectl apply -k ./deploy/kubernetes/overlays/edgeless/latest
   ```

2. Create a [storage class](https://kubernetes.io/docs/concepts/storage/storage-classes/) for your driver

    A storage class configures the driver responsible for provisioning storage for persistent volume claims.
    A storage class only needs to be created once and can then be used by multiple volumes.
    The following snippet creates a simple storage class for the GCE PD driver, utilizing [balanced persistent disks](https://cloud.google.com/compute/docs/disks#pdspecs) as the storage backend device when the first Pod claiming the volume is created.

    ```bash
    cat <<EOF | kubectl apply -f -
    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: encrypted-storage
      annotations:
        storageclass.kubernetes.io/is-default-class: "true"
    provisioner: gcp.csi.confidential.cloud
    parameters:
      type: pd-standard
    reclaimPolicy: Delete
    volumeBindingMode: WaitForFirstConsumer
    EOF
    ```

:::info

By default, integrity protection is disabled for performance reasons. If you want to enable integrity protection, add `csi.storage.k8s.io/fstype: ext4-integrity` to `parameters`. Alternatively, you can use another filesystem by specifying another file system type with the suffix `-integrity`. Note that volume expansion isn't supported for integrity-protected disks.

:::

</tabItem>
</tabs>

3. Create a [persistent volume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/)

    A [persistent volume claim](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims) is a request for storage with certain properties.
    It can refer to a storage class.
    The following creates a persistent volume claim, requesting 20 GB of storage via the previously created storage class:

    ```bash
    cat <<EOF | kubectl apply -f -
    kind: PersistentVolumeClaim
    apiVersion: v1
    metadata:
      name: pvc-example
      namespace: default
    spec:
      accessModes:
      - ReadWriteOnce
      storageClassName: encrypted-storage
      resources:
        requests:
          storage: 20Gi
    EOF
    ```

4. Create a Pod with persistent storage

    You can assign a persistent volume claim to an application in need of persistent storage.
    The mounted volume will persist restarts.
    The following creates a pod that uses the previously created persistent volume claim:

    ```bash
    cat <<EOF | kubectl apply -f -
    apiVersion: v1
    kind: Pod
    metadata:
      name: web-server
      namespace: default
    spec:
      containers:
      - name: web-server
        image: nginx
        volumeMounts:
        - mountPath: /var/lib/www/html
          name: mypvc
      volumes:
      - name: mypvc
        persistentVolumeClaim:
          claimName: pvc-example
          readOnly: false
    EOF
    ```

### Set the default storage class
The examples above are defined to be automatically set as the default storage class. The default storage class is responsible for all persistent volume claims that don't explicitly request `storageClassName`. In case you need to change the default, follow the steps below:

<tabs groupId="csp">
<tabItem value="azure" label="Azure" default>

  1. List the storage classes in your cluster:

      ```bash
      kubectl get storageclass
      ```

      The output is similar to this:

      ```shell-session
      NAME                      PROVISIONER                       AGE
      some-storage (default)    disk.csi.azure.com                1d
      encrypted-storage         azuredisk.csi.confidential.cloud  1d
      ```

      The default storage class is marked by `(default)`.

  2. Mark old default storage class as non default

      If you previously used another storage class as the default, you will have to remove that annotation:

      ```bash
      kubectl patch storageclass <name-of-old-default> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
      ```

  3. Mark new class as the default

      ```bash
      kubectl patch storageclass <name-of-new-default> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
      ```

  4. Verify that your chosen storage class is default:

      ```bash
      kubectl get storageclass
      ```

      The output is similar to this:

      ```shell-session
      NAME                        PROVISIONER                       AGE
      some-storage                disk.csi.azure.com                1d
      encrypted-storage (default) azuredisk.csi.confidential.cloud  1d
      ```

</tabItem>
<tabItem value="gcp" label="GCP" default>

  1. List the storage classes in your cluster:

      ```bash
      kubectl get storageclass
      ```

      The output is similar to this:

      ```shell-session
      NAME                      PROVISIONER                 AGE
      some-storage (default)    pd.csi.storage.gke.io       1d
      encrypted-storage         gcp.csi.confidential.cloud  1d
      ```

      The default storage class is marked by `(default)`.

  2. Mark old default storage class as non default

      If you previously used another storage class as the default, you will have to remove that annotation:

      ```bash
      kubectl patch storageclass <name-of-old-default> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
      ```

  3. Mark new class as the default

      ```bash
      kubectl patch storageclass <name-of-new-default> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
      ```

  4. Verify that your chosen storage class is default:

      ```bash
      kubectl get storageclass
      ```

      The output is similar to this:

      ```shell-session
      NAME                        PROVISIONER                 AGE
      some-storage                pd.csi.storage.gke.io       1d
      encrypted-storage (default) gcp.csi.confidential.cloud  1d
      ```

</tabItem>
</tabs>
