CentOS 7 Virtual Server with LUKS Encryption

An example Virtual Server running CentOS 7 with LUKS encryption

The following example demonstrates how to deploy a Virtual Server running CentOS 7 with an encrypted partition on the root disk. The method used for this example is to deploy a manifest using the Kubernetes command line.

The manifest used to deploy this Virtual Server includes cloud-init directives, which are used to encrypt unallocated space on the root disk. By using a separate partition instead of a separate block volume, additional Virtual Servers can be created by cloning the encrypted partition from the same disk.

This process essentially consists of configuring a Deployment manifest for the Virtual Server, which includes several critical cloud-init directives, and then deploying it.

Configure the Deployment manifest

The following manifest will be used to deploy the CentOS 7 Virtual Server. However, a few properties need to be adjusted prior to deployment.

Click to expand - centos-7-luks-partition.yml
apiVersion: virtualservers.coreweave.com/v1alpha1
kind: VirtualServer
metadata:
  name: centos7-luks
spec:
  region: LGA1
  os:
    type: linux
  resources:
    gpu:
      type: Quadro_RTX_4000
      count: 1
    cpu:
      count: 4
    memory: 16Gi
  storage:
    root:
      size: 40Gi
      storageClassName: block-nvme-lga1
      source:
        pvc:
          namespace: vd-images
          # Reference querying source image here:
          # https://docs.coreweave.com/virtual-servers/coreweave-system-images
          name: centos7-nvidia-510-85-02-docker-master-20220831-lga1
  users:
  - sshpublickey: ssh-rsa AAAAB3NzaC1yc2EAAAA ... user@hostname
    username: user
  network:
    directAttachLoadBalancerIP: false
    public: true
    tcp:
      ports:
      - 22
  cloudInit: |
    runcmd:
      - [bash, -c, sgdisk -n 2:34:2047 -t 2:ef02 /dev/vda -g ]
      - [bash, -c, partprobe /dev/vda ]
      - [bash, -c, grub2-install /dev/vda ]
      - [bash, -c, (echo "@reboot /encrypt_init.sh")| crontab - ]
      - [bash, -c, touch /etc/cloud/cloud-init.disabled ]
      - [bash, -c, shutdown -h +1 ]
    write_files:
      - content: |
          #!/bin/bash
          PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
          sed -i 's/growpart,\ always/growpart,\ once-per-instance/g' /etc/cloud/cloud.cfg
          sed -i 's/resizefs,\ always/resizefs,\ once-per-instance/g' /etc/cloud/cloud.cfg
          rm -rf /var/lib/cloud/instances/*/scripts/runcmd
          rm -f /etc/cloud/cloud-init.disabled
   
          sgdisk -e /dev/vda
          partprobe /dev/vda
          sgdisk -n 3::0 /dev/vda
          partprobe /dev/vda
   
          phrase=$(openssl rand -base64 32)
          echo -n $phrase | cryptsetup -q luksFormat /dev/vda3 -
          echo -n $phrase | cryptsetup -q luksOpen /dev/vda3 CryptedPart1 -
          dd if=/dev/urandom of=/root/keyfile1 bs=1024 count=4
          chmod 0400 /root/keyfile1
          echo -n $phrase | cryptsetup luksAddKey /dev/vda3 /root/keyfile1 -
          mkfs.ext4 /dev/mapper/CryptedPart1
          mkdir /encryptedfs
          echo "/dev/mapper/CryptedPart1 /encryptedfs ext4 defaults 1 2" >> /etc/fstab
          echo "CryptedPart1 /dev/vda3 /root/keyfile1 luks" >> /etc/crypttab
          mount -a
          echo -n $phrase | cryptsetup -q luksRemoveKey /dev/vda3 -
          (echo "")| crontab -
          rm -- "$0"
        owner: root:root
        path: /encrypt_init.sh
        permissions: '0755'
  initializeRunning: true
  

Root disk size

.spec.storage.root.size

First, the desired size of the unencrypted portion of the root disk, where the Operating System resides, must be specified. After initialization, expanding this part of the disk will require manual re-partitioning.

In this example, the given value for the disk size is 40Gi.

  storage:
    root:
      size: 40Gi

Root disk image

.spec.storage.root.source.pvc.name

The root disk image source must be specified. It is best practice to always use the latest available root disk image.

The latest CentOS 7 image with NVIDIA drivers can be found by invoking the following command:

$ kubectl get pvc -n vd-images -l images.coreweave.cloud/latest=true,images.coreweave.cloud/private=false,images.coreweave.cloud/name=CentOS_7,images.coreweave.cloud/region=lga1 -o=custom-columns="PVC:metadata.name,NAME:metadata.labels['images\.coreweave\.cloud\/name'],FEATURES:metadata.labels['images\.coreweave\.cloud\/features'],SIZE:status.capacity.storage,STORAGECLASS:.spec.storageClassName" --sort-by='.metadata.name'

In this example, the value given is centos7-nvidia-510-85-02-docker-master-20220831-lga1.

  storage:
    root:
      size: 40Gi
      storageClassName: block-nvme-lga1
      source:
        pvc:
          namespace: vd-images
          # Reference querying source image here:
          # https://docs.coreweave.com/virtual-servers/coreweave-system-images
          name: centos7-nvidia-510-85-02-docker-master-20220831-lga1

Additional Resources

For more information for querying disk image sources, see System Images.

Users

.spec.users[]

Next, the user accounts for the Virtual Server are specified in the .users array. In this example, users are authenticated using SSH keys (sshpublickey) only.

In the sshpublickey field, use the user's personal public key, along with a desired username set in the username field.

In this example, only one user is added with the username of user and a valid truncated key for the SSH key value:

  - sshpublickey: ssh-rsa AAAAB3NzaC1yc2EAAAA ... user@hostname
    username: user

More than one user may be added. See User Accounts for more information.

cloud-init

Preparing the Virtual Server for an encrypted partition requires some pre-requisite steps, which are handled, along with encryption configuration, by cloud-init.

Click to expand - cloud-init directives excerpted from the manifest
  cloudInit: |
    runcmd:
      - [bash, -c, sgdisk -n 2:34:2047 -t 2:ef02 /dev/vda -g ]
      - [bash, -c, partprobe /dev/vda ]
      - [bash, -c, grub2-install /dev/vda ]
      - [bash, -c, (echo "@reboot /encrypt_init.sh")| crontab - ]
      - [bash, -c, touch /etc/cloud/cloud-init.disabled ]
      - [bash, -c, shutdown -h +1 ]
    write_files:
      - content: |
          #!/bin/bash
          PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
          sed -i 's/growpart,\ always/growpart,\ once-per-instance/g' /etc/cloud/cloud.cfg
          sed -i 's/resizefs,\ always/resizefs,\ once-per-instance/g' /etc/cloud/cloud.cfg
          rm -rf /var/lib/cloud/instances/*/scripts/runcmd
          rm -f /etc/cloud/cloud-init.disabled
   
          sgdisk -e /dev/vda
          partprobe /dev/vda
          sgdisk -n 3::0 /dev/vda
          partprobe /dev/vda
   
          phrase=$(openssl rand -base64 32)
          echo -n $phrase | cryptsetup -q luksFormat /dev/vda3 -
          echo -n $phrase | cryptsetup -q luksOpen /dev/vda3 CryptedPart1 -
          dd if=/dev/urandom of=/root/keyfile1 bs=1024 count=4
          chmod 0400 /root/keyfile1
          echo -n $phrase | cryptsetup luksAddKey /dev/vda3 /root/keyfile1 -
          mkfs.ext4 /dev/mapper/CryptedPart1
          mkdir /encryptedfs
          echo "/dev/mapper/CryptedPart1 /encryptedfs ext4 defaults 1 2" >> /etc/fstab
          echo "CryptedPart1 /dev/vda3 /root/keyfile1 luks" >> /etc/crypttab
          mount -a
          echo -n $phrase | cryptsetup -q luksRemoveKey /dev/vda3 -
          (echo "")| crontab -
          rm -- "$0"
        owner: root:root
        path: /encrypt_init.sh
        permissions: '0755'

The following aspects are handled by cloud-init in this manifest:

GPT Configuration

By default, Virtual Server images are configured with an Master Boot Record (MBR) partition table, which does not support sizes above 2Ti. Because of this, the disk needs to be converted to a GUID Partition Table (GPT). This process also requires re-partitioning, as GRUB must reside on a BIOS boot partition.

All of this is accomplished with a few commands in the runcmd cloud-init directive:

 cloudInit: |
    runcmd:
      - [bash, -c, sgdisk -n 2:34:2047 -t 2:ef02 /dev/vda -g ]
      - [bash, -c, partprobe /dev/vda ]
      - [bash, -c, grub2-install /dev/vda ]

Startup script bootstrapping

By default, cloudInit automatically expands the root disk partition to use all available unallocated space.

Because of this, the space allocated to the Virtual Server at creation time will automatically be used for the Operating System partition. To prevent this from happening on subsequent restarts after the disk has been expanded for the encrypted partition, we need to temporarily disable cloudInit.

Since the Virtual Server needs to be powered off to expand its disk, we need to inject a script using the write_files cloud-init directive to do so, then instruct it to run at next start up using the runcmd directive:

  - [bash, -c, (echo "@reboot /encrypt_init.sh")| crontab - ]
  - [bash, -c, touch /etc/cloud/cloud-init.disabled ]
  - [bash, -c, shutdown -h +1 ]

Configuration startup script

After expanding the root disk and starting the Virtual Server back up, the script called encrypt_init.sh , which was injected via write_files, completes the configuration.

Then, cloud-init is re-enabled for subsequent reboots once the growpart and resizefs modules are disabled:

   write_files:
      - content: |
          #!/bin/bash
          PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
          sed -i 's/growpart,\ always/growpart,\ once-per-instance/g' /etc/cloud/cloud.cfg
          sed -i 's/resizefs,\ always/resizefs,\ once-per-instance/g' /etc/cloud/cloud.cfg
          rm -rf /var/lib/cloud/instances/*/scripts/runcmd
          rm -f /etc/cloud/cloud-init.disabled

The GPT header is then moved to the expanded end of disk, and a new partition is created with the new unallocated space:

          sgdisk -e /dev/vda
          partprobe /dev/vda
          sgdisk -n 3::0 /dev/vda
          partprobe /dev/vda

This new partition is initialized with LUKS, using a temporary key:

          phrase=$(openssl rand -base64 32)
          echo -n $phrase | cryptsetup -q luksFormat /dev/vda3 -
          echo -n $phrase | cryptsetup -q luksOpen /dev/vda3 CryptedPart1 -

A key file is generated and secured, so the partition can be auto-mounted at boot:

          dd if=/dev/urandom of=/root/keyfile1 bs=1024 count=4
          chmod 0400 /root/keyfile1
          echo -n $phrase | cryptsetup luksAddKey /dev/vda3 /root/keyfile1 -

The encrypted partition is formatted to ext4, added to fstab, and then mounted:

          mkdir /encryptedfs
          echo "/dev/mapper/CryptedPart1 /encryptedfs ext4 defaults 1 2" >> /etc/fstab
          echo "CryptedPart1 /dev/vda3 /root/keyfile1 luks" >> /etc/crypttab
          mount -a

The initial temporary key is removed, the startup script is removed from crontab, and finally, the script deletes itself:

          echo -n $phrase | cryptsetup -q luksRemoveKey /dev/vda3 -
          (echo "")| crontab -
          rm -- "$0"

The script's output can be found in/var/mail/root, and can be viewed using cat.

Deploy the Virtual Server

With the manifest fully configured and the cloud-init directives in place, the Virtual Server is deployed using kubectl:

$ kubectl apply -f centos-7-luks-part.yaml

One of the cloudInit directives powers off the Virtual Server after it completes initialization so the disk can be expanded for the encrypted partition.

Deployment progress may be monitored by invoking kubectl --watch:

$ kubectl get vs centos7-luks --watch

NAME           STATUS    REASON                                                    STARTED   INTERNAL IP   EXTERNAL IP
centos7-luks   Pending   Waiting for DataVolume to be ready - CSICloneInProgress   False                   216.153.61.34
centos7-luks   Pending   Waiting for VirtualMachineInstance to be ready            False                   216.153.61.34
centos7-luks   Pending   Waiting for VirtualMachine to be ready                    False                   216.153.61.34
centos7-luks   Pending   virt-launcher pod has not yet been scheduled              False                   216.153.61.34
centos7-luks   Pending   Guest VM is not reported as running                       False                   216.153.61.34
centos7-luks   Pending   Guest VM is not reported as running                       False     10.147.97.61   216.153.61.34
centos7-luks   VirtualServerReady   VirtualServerReady                                        True      10.147.97.61   216.153.61.34
centos7-luks   VirtualServerReady   VirtualServerReady                                        True      10.147.97.61   216.153.61.34
centos7-luks   Pending              virt-launcher pod is terminating                          False     10.147.97.61   216.153.61.34
centos7-luks   Pending              virt-launcher pod is terminating                          False     10.147.97.61   216.153.61.34
centos7-luks   VirtualMachineInstanceShutdown   VirtualMachineInstance stopped                            False     10.147.97.61   216.153.61.34

Once the Virtual Server is created and spun down, the root disk may be expanded to create the encrypted partition.

In this example, the desired size of the encrypted volume is 5Ti. Since the root disk size was initially set to 40Gi, the Virtual Server is expanded to a total size of 5040Gi using kubectl patch:

$ kubectl patch vs centos7-luks -p '{"spec":{"storage":{"root":{"size": "5040Gi"}}}}' --type=merge

With the root disk expanded, the Virtual Server can be started up again:

$ virtctl start centos7-luks

VM centos7-luks was scheduled to start

Confirm the disk state

Confirming that the encrypted partition has been created, formatted, and mounted is achieved by SSHing into the Virtual Server and running df -h:

$ df -h

Filesystem                Size  Used Avail Use% Mounted on

devtmpfs                  7.8G     0  7.8G   0% /dev
tmpfs                     7.9G     0  7.9G   0% /dev/shm
tmpfs                     7.9G   18M  7.9G   1% /run
tmpfs                     7.9G     0  7.9G   0% /sys/fs/cgroup
/dev/vda1                  40G  6.7G   34G  17% /
/dev/mapper/CryptedPart1  4.9T   24K  4.6T   1% /encryptedfs
tmpfs                     1.6G     0  1.6G   0% /run/user/1000

Last updated