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/v1alpha1kind: VirtualServermetadata:name: centos7-luksspec:region: LGA1os:type: linuxresources:gpu:type: Quadro_RTX_4000count: 1cpu:count: 4memory: 16Gistorage:root:size: 40GistorageClassName: block-nvme-lga1source:pvc:namespace: vd-images# Reference querying source image here:# https://docs.coreweave.com/virtual-servers/coreweave-system-imagesname: centos7-nvidia-510-85-02-docker-master-20220831-lga1users:- sshpublickey: ssh-rsa AAAAB3NzaC1yc2EAAAA ... user@hostnameusername: usernetwork:directAttachLoadBalancerIP: falsepublic: truetcp:ports:- 22cloudInit: |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/bashPATH="/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.cfgsed -i 's/resizefs,\ always/resizefs,\ once-per-instance/g' /etc/cloud/cloud.cfgrm -rf /var/lib/cloud/instances/*/scripts/runcmdrm -f /etc/cloud/cloud-init.disabledsgdisk -e /dev/vdapartprobe /dev/vdasgdisk -n 3::0 /dev/vdapartprobe /dev/vdaphrase=$(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=4chmod 0400 /root/keyfile1echo -n $phrase | cryptsetup luksAddKey /dev/vda3 /root/keyfile1 -mkfs.ext4 /dev/mapper/CryptedPart1mkdir /encryptedfsecho "/dev/mapper/CryptedPart1 /encryptedfs ext4 defaults 1 2" >> /etc/fstabecho "CryptedPart1 /dev/vda3 /root/keyfile1 luks" >> /etc/crypttabmount -aecho -n $phrase | cryptsetup -q luksRemoveKey /dev/vda3 -(echo "")| crontab -rm -- "$0"owner: root:rootpath: /encrypt_init.shpermissions: '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: 40GistorageClassName: block-nvme-lga1source:pvc:namespace: vd-images# Reference querying source image here:# https://docs.coreweave.com/virtual-servers/coreweave-system-imagesname: centos7-nvidia-510-85-02-docker-master-20220831-lga1
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@hostnameusername: 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/bashPATH="/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.cfgsed -i 's/resizefs,\ always/resizefs,\ once-per-instance/g' /etc/cloud/cloud.cfgrm -rf /var/lib/cloud/instances/*/scripts/runcmdrm -f /etc/cloud/cloud-init.disabledsgdisk -e /dev/vdapartprobe /dev/vdasgdisk -n 3::0 /dev/vdapartprobe /dev/vdaphrase=$(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=4chmod 0400 /root/keyfile1echo -n $phrase | cryptsetup luksAddKey /dev/vda3 /root/keyfile1 -mkfs.ext4 /dev/mapper/CryptedPart1mkdir /encryptedfsecho "/dev/mapper/CryptedPart1 /encryptedfs ext4 defaults 1 2" >> /etc/fstabecho "CryptedPart1 /dev/vda3 /root/keyfile1 luks" >> /etc/crypttabmount -aecho -n $phrase | cryptsetup -q luksRemoveKey /dev/vda3 -(echo "")| crontab -rm -- "$0"owner: root:rootpath: /encrypt_init.shpermissions: '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/bashPATH="/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.cfgsed -i 's/resizefs,\ always/resizefs,\ once-per-instance/g' /etc/cloud/cloud.cfgrm -rf /var/lib/cloud/instances/*/scripts/runcmdrm -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/vdapartprobe /dev/vdasgdisk -n 3::0 /dev/vdapartprobe /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=4chmod 0400 /root/keyfile1echo -n $phrase | cryptsetup luksAddKey /dev/vda3 /root/keyfile1 -
The encrypted partition is formatted to ext4
, added to fstab
, and then mounted:
mkdir /encryptedfsecho "/dev/mapper/CryptedPart1 /encryptedfs ext4 defaults 1 2" >> /etc/fstabecho "CryptedPart1 /dev/vda3 /root/keyfile1 luks" >> /etc/crypttabmount -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 --watchNAME STATUS REASON STARTED INTERNAL IP EXTERNAL IPcentos7-luks Pending Waiting for DataVolume to be ready - CSICloneInProgress False 216.153.61.34centos7-luks Pending Waiting for VirtualMachineInstance to be ready False 216.153.61.34centos7-luks Pending Waiting for VirtualMachine to be ready False 216.153.61.34centos7-luks Pending virt-launcher pod has not yet been scheduled False 216.153.61.34centos7-luks Pending Guest VM is not reported as running False 216.153.61.34centos7-luks Pending Guest VM is not reported as running False 10.147.97.61 216.153.61.34centos7-luks VirtualServerReady VirtualServerReady True 10.147.97.61 216.153.61.34centos7-luks VirtualServerReady VirtualServerReady True 10.147.97.61 216.153.61.34centos7-luks Pending virt-launcher pod is terminating False 10.147.97.61 216.153.61.34centos7-luks Pending virt-launcher pod is terminating False 10.147.97.61 216.153.61.34centos7-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-luksVM 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 -hFilesystem Size Used Avail Use% Mounted ondevtmpfs 7.8G 0 7.8G 0% /devtmpfs 7.9G 0 7.9G 0% /dev/shmtmpfs 7.9G 18M 7.9G 1% /runtmpfs 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% /encryptedfstmpfs 1.6G 0 1.6G 0% /run/user/1000