diff --git a/confidential-data-hub/docs/SECURE_STORAGE.md b/confidential-data-hub/docs/SECURE_STORAGE.md index 87bf49384..83826f365 100644 --- a/confidential-data-hub/docs/SECURE_STORAGE.md +++ b/confidential-data-hub/docs/SECURE_STORAGE.md @@ -62,3 +62,28 @@ flowchart LR ``` For more details, please refer to [the guide](use-cases/secure-mount-with-aliyun-oss.md). + +### Block Device + +The [plugin](../storage/src/volume_type/blockdevice) provides ways to encrypt a block device and mount it to a specific mount point. Currently only support LUKS in [cryptsetup](https://gitlab.com/cryptsetup/cryptsetup/) for block device encryption. + +#### LUKS Encryption + +In this mode, the device would be encrypted as LUKS device first, and then mount it to a target path to store the data to protect the confidentiality and integrity of the data. + +The architecture diagram is + +```mermaid +flowchart LR + A[Local/Network] -- mount --> B[Block Device] + subgraph TEE Guest + B -- Check if encrypted --> F{Is Encrypted?} + F -- No --> G[Encrypt by cryptsetup] + G -- encrypt --> C[LUKS Encrypted Block Device] + F -- Yes --> C[LUKS Encrypted Block Device] + C -- open and mapping --> D[Mapped Device] + D -- mount --> E[Target Path] + end +``` + +For more details, please refer to [the guide](use-cases/secure-mount-with-block-device.md). diff --git a/confidential-data-hub/docs/use-cases/secure-mount-with-block-device.md b/confidential-data-hub/docs/use-cases/secure-mount-with-block-device.md new file mode 100644 index 000000000..ac626da08 --- /dev/null +++ b/confidential-data-hub/docs/use-cases/secure-mount-with-block-device.md @@ -0,0 +1,77 @@ +# Secure mount with Block Device + +This guide helps an user to use confidential data hub to secure mount inside TEE environment. + +## Preliminaries + +- Ensure that [cryptsetup](https://gitlab.com/cryptsetup/cryptsetup/) is installed. + +## Example + +### Create a loop device + +```shell +$ loop_file="/tmp/test.img" +$ sudo dd if=/dev/zero of=$loop_file bs=1M count=1000 +$ sudo losetup -fP $loop_file +$ device=$(sudo losetup -j $loop_file | awk -F'[: ]' '{print $1}') +$ echo $device +# Output should be something like /dev/loop0 +$ device_num=$(sudo lsblk -no MAJ:MIN $device) +$ echo $device_num +# Output should be something like 7:0 +``` + +### Secure mount inside a TEE environment + +1. Build the CDH and its client tool + +Follow the instructions in the [CDH README](../../README.md#confidential-data-hub) and [Client Tool README](../../README.md#client-tool) to build the CDH and its client tool. + +2. Install `luks-encrypt-storage` + +Install [luks-encrypt-storage](../../storage/scripts/luks-encrypt-storage) into `/usr/local/bin` + +3. Run CDH +```shell +$ confidential-data-hub +``` + +4. Prepare a request JSON `storage.json` +```json +{ + { + "volume_type": "BlockDevice", + "options": { + "deviceId": "7:0", + "encryptType": "LUKS", + "dataIntegrity": "true" + }, + "flags": [], + "mount_point": "/mnt/test-path" + } +} + +``` +- Fields: + - `volume_type`: The secure mount plugin type name. It determines how the rest of the fields are used. + - `options`: A key-value map specifying the settings for the mount operation. Different plugins can define different keys in the options. In this example, all keys are for block devices. + - `flags`: A string list specifying settings for the mount operation. Different plugins can define different uses for this field. + - `mount_point`: The target mount path for the operation. + +- Options Fields: + - `deviceId`: The device number, formatted as "MAJ:MIN". + - `encryptType`: The encryption type. Currently, only LUKS is supported. + - `encryptKey`: Encryption key. It can be a sealed secret or a resource uri. If not set, it means that the device is unencrypted and a random 4096-byte key will be generated to encrypt the device. + - `dataIntegrity`: Enables dm-integrity to protect data integrity. Note that enabling data integrity will reduce IO performance by more than 30%. + +5. Make a request to CDH +```shell +$ client-tool secure-mount --storage-path storage.json + +# Check the target path to see if the mount succeeded +$ lsblk |grep "encrypted_disk" +# Expected output: +└─encrypted_disk_OEyEj_dif 253:1 0 968.6M 0 crypt + └─encrypted_disk_OEyEj 253:2 0 968.6M 0 crypt /mnt/test-path +``` \ No newline at end of file diff --git a/confidential-data-hub/storage/scripts/luks-encrypt-storage b/confidential-data-hub/storage/scripts/luks-encrypt-storage new file mode 100755 index 000000000..a85d82374 --- /dev/null +++ b/confidential-data-hub/storage/scripts/luks-encrypt-storage @@ -0,0 +1,149 @@ +#!/bin/bash +# +# Copyright (c) 2022 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +set -o errexit +set -o nounset +set -o pipefail +set -o errtrace + +[ -n "${DEBUG:-}" ] && set -o xtrace + +handle_error() { + local exit_code="${?}" + local line_number="${1:-}" + echo "error:" + echo "Failed at $line_number: ${BASH_COMMAND}" + exit "${exit_code}" +} +trap 'handle_error $LINENO' ERR + +die() +{ + local msg="$*" + echo >&2 "ERROR: $msg" + exit 1 +} + +setup() +{ + local cmds=() + + cmds+=("cryptsetup" "mkfs.ext4" "mount") + + local cmd + for cmd in "${cmds[@]}" + do + command -v "$cmd" &>/dev/null || die "need command: '$cmd'" + done +} + +setup + +device_num=${1:-} +if [ -z "$device_num" ]; then + die "invalid arguments, at least one param for device num" +fi + +is_encrypted="false" +if [ -n "${2-}" ]; then + is_encrypted="$2" +fi + +mount_point="/tmp/target_path" +if [ -n "${3-}" ]; then + mount_point="$3" +fi + +storage_key_path="/run/encrypt_storage.key" +if [ -n "${4-}" ]; then + storage_key_path="$4" +fi + +data_integrity="true" +if [ -n "${5-}" ]; then + data_integrity="$5" +fi + +device_name=$(sed -e 's/DEVNAME=//g;t;d' "/sys/dev/block/${device_num}/uevent") +device_path="/dev/$device_name" + +opened_device_name=$(mktemp "encrypted_disk_XXXXX") + +if [[ -n "$device_name" && -b "$device_path" ]]; then + + if [ "$is_encrypted" == "false" ]; then + + if [ "$data_integrity" == "false" ]; then + echo "YES" | cryptsetup luksFormat --type luks2 "$device_path" --sector-size 4096 \ + --cipher aes-xts-plain64 "$storage_key_path" + else + # Wiping a device is a time consuming operation. To avoid a full wipe, integritysetup + # and crypt setup provide a --no-wipe option. + # However, an integrity device that is not wiped will have invalid checksums. Normally + # this should not be a problem since a page must first be written to before it can be read + # (otherwise the data would be arbitrary). The act of writing would populate the checksum + # for the page. + # However, tools like mkfs.ext4 read pages before they are written; sometimes the read + # of an unwritten page happens due to kernel buffering. + # See https://gitlab.com/cryptsetup/cryptsetup/-/issues/525 for explanation and fix. + # The way to propery format the non-wiped dm-integrity device is to figure out which pages + # mkfs.ext4 will write to and then to write to those pages before hand so that they will + # have valid integrity tags. + echo "YES" | cryptsetup luksFormat --type luks2 "$device_path" --sector-size 4096 \ + --cipher aes-xts-plain64 --integrity hmac-sha256 "$storage_key_path" \ + --integrity-no-wipe + fi + fi + + cryptsetup luksOpen -d "$storage_key_path" "$device_path" $opened_device_name + rm "$storage_key_path" + + if [ "$data_integrity" == "false" ]; then + mkfs.ext4 /dev/mapper/$opened_device_name -E lazy_journal_init + else + # mkfs.ext4 doesn't perform whole sector writes and this will cause checksum failures + # with an unwiped integrity device. Therefore, first perform a dry run. + output=$(mkfs.ext4 /dev/mapper/$opened_device_name -F -n) + + # The above command will produce output like + # mke2fs 1.46.5 (30-Dec-2021) + # Creating filesystem with 268435456 4k blocks and 67108864 inodes + # Filesystem UUID: 4a5ff012-91c0-47d9-b4bb-8f83e830825f + # Superblock backups stored on blocks: + # 32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208, + # 4096000, 7962624, 11239424, 20480000, 23887872, 71663616, 78675968, + # 102400000, 214990848 + delimiter="Superblock backups stored on blocks:" + blocks_list=$([[ $output =~ $delimiter(.*) ]] && echo "${BASH_REMATCH[1]}") + + # Find list of blocks + block_nums=$(echo "$blocks_list" | grep -Eo '[0-9]{4,}' | sort -n) + + # Add zero to list of blocks + block_nums="0 $block_nums" + + # Iterate through each block and write to it to ensure that it has valid checksum + for block_num in $block_nums + do + echo "Clearing page at $block_num" + # Zero out the page + dd if=/dev/zero bs=4k count=1 oflag=direct \ + of=/dev/mapper/$opened_device_name seek="$block_num" + done + + # Now perform the actual ext4 format. Use lazy_journal_init so that the journal is + # initialized on demand. This is safe for ephemeral storage since we don't expect + # ephemeral storage to survice a power cycle. + mkfs.ext4 /dev/mapper/$opened_device_name -E lazy_journal_init + fi + + [ ! -d "$mount_point" ] && mkdir -p $mount_point + + mount /dev/mapper/$opened_device_name $mount_point +else + die "Invalid device: '$device_path'" +fi \ No newline at end of file