Exoscale Flexible Storage template empowers users to resize and/or create disk partitions as they deem fit, thanks to the flexibility provided by the Linux Logical Volume Manager (LVM).
In this article, we will leverage this flexibility to create a new partition, encrypted using Linux Unified Key Setup (LUKS), which passphrase shall be secured using HashiCorp Vault.
To achieve this objective, we will, in the following sections:
- configure HashiCorp Vault to:
- provide Encryption-as-a-Service to secure the LUKS passphrase
-
perform seamless authentication of compute instances, using Exoscale Vault Authentication Plugin
-
create and encrypt a new LVM Logical Volume (LV) / partition using LUKS
-
setup the bits’-n-pieces (script, systemd unit) required to automatically - yet securely - enable the LUKS encrypted partition at boot time and disable it on shutdown
All of it using Exoscale CLI to interact with Exoscale cloud platform.
Configure your Vault server
We will assume that you already have a running Vault server available (which setup is out of the scope of this article).
Vault Encryption-as-a-Service
In order to secure the LUKS passphrase, we will use Vault Transit Secrets Engine, enabling so-called Encryption-as-a-Service (EaaS):
# Enable Vault Transit Secrets Engine
$ vault secrets enable transit
# [output]
Success! Enabled the transit secrets engine at: transit/
Along a LUKS (passphrase) dedicated encryption/decryption endpoint/key and ad-hoc Policy:
# Create LUKS-dedicated encryption/decryption endpoint/key
$ vault write -f transit/keys/luks
# [output]
Success! Data written to: transit/keys/luks
# Create ad-hoc Policy
$ vault policy write 'transit-luks' - <<EOF
# Allow Policy holder to encrypt LUKS passphrase
path "transit/encrypt/luks"
{
capabilities = ["update"]
}
# Allow Policy holder to decrypt LUKS passphrase
path "transit/decrypt/luks"
{
capabilities = ["update"]
}
EOF
# [output]
Success! Uploaded policy: transit-luks
Vault Exoscale Authentication Plugin
Vault Exoscale Authentication Plugin allows to authenticate Exoscale Compute Instances based on properties of each instance, whether intrinsic (set by the Exoscale cloud platform; e.g. the instance UUID) or assigned by the users (such as instance tags).
Dedicated Exoscale IAM credentials
In order to access Exoscale API and verify/match compute instances properties, the Vault Exoscale Authentication Plugin must be configured with Exoscale IAM credentials, ideally scoped specifically for the purpose at hand:
# Create Vault-specific Exoscale IAM credentials
$ exo iam apikey create vault-plugin-auth-exoscale \
--operation 'compute/listZones,compute/listVirtualMachines'
# [output (partial)]
Name vault-plugin-auth-exoscale
Key EXO...
Secret ...
Mark the output Key
and Secret
as we will need them in the next section.
Setup the Vault Exoscale Authentication Plugin
As any other Vault plugin, the Vault Exoscale Authentication Plugin must be registered before it can
be used. Make sure to add the plugin_directory = "/usr/lib/vault/server/plugins"
to your
Vault server’s configuration. Then:
# Register the Exoscale Authentication Plugin
$ vault plugin register \
-command="vault-plugin-auth-exoscale" \
-sha256="$(sha256sum /usr/lib/vault/server/plugins/vault-plugin-auth-exoscale | cut -d " " -f 1)" \
auth exoscale
# [output]
Success! Registered plugin: exoscale
You may then enable and configure it with the Exoscale IAM credentials we created above:
# Enable the Exoscale Authentication Plugin
$ vault auth enable exoscale
# [output]
Success! Enabled exoscale auth method at: exoscale/
# Configure the Exoscale Authentication Plugin
$ vault write auth/exoscale/config api_key=EXO... api_secret=...
$ vault read auth/exoscale/config
# [output]
Key Value
--- -----
api_endpoint https://api.exoscale.com/v1
api_key EXO...
api_secret ...
Configure a Vault Exoscale Authentication Role
In order to grant our storage instance the proper permissions, we shall:
- authenticate it using the validator
parameter and a CEL expression that matches
any property you may deem fit; e.g. the instance name
- authorize it by granting it the transit-luks
Policy (token_policies
)
$ vault write auth/exoscale/role/storage \
validator='instance_name.startsWith("storage")' \
token_period='24h' \
token_policies='transit-luks'
# [output]
Success! Data written to: auth/exoscale/role/storage
Mark the Token being a Periodic Token, which will come handy for the automation part (see section further below).
Create and encrypt a new LVM Logical Volume / partition
Please refer to the Exoscale Flexible Storage template documentation to create an Exoscale Flexible Storage instance and get acquainted with its (re)partitioning.
Authenticate with the Vault server
Let’s start by authenticating with the Vault server:
# Retrieve a Vault Token ("login")
# (NB: at the time of writing 'vault login -method=exoscale' is not yet supported)
$ umask 077
$ vault write -field=token auth/exoscale/login \
role=storage \
instance=$(wget -qO- http://metadata.exoscale.com/1.0/meta-data/instance-id) \
zone=$(wget -qO- http://metadata.exoscale.com/1.0/meta-data/availability-zone) \
> ~/.vault-token
# Verify login/permissions
$ vault token lookup
# [output (partial)]
Key Value
--- -----
display_name exoscale
path auth/exoscale/login
policies [default transit-luks]
Create the LUKS passphrase
We may now create a random LUKS passphrase and secure it with Vault:
# Create and Vault-encrypt the LUKS passphrase
$ openssl rand -base64 24 \
| vault write -field=ciphertext transit/encrypt/luks plaintext=- \
| tee /etc/luks.passphrase
# [output]
vault:v1:lX3HqaqNdKbB2uD4yfcK8CfWsv/ugRFrTzJSrRFiuowpluFJ9nyGKbPq+HiyJINaq0SOEw==
# Decrypt and backup (!) the LUKS passphrase
$ cat /etc/luks.passphrase \
| vault write -field=plaintext transit/decrypt/luks ciphertext=-
# [output]
kDTAmQFldH0J0yhu3M2zfvHTOZLJMtTY # !!! SECRET !!!
Mark the /etc/luks.passphrase
being encrypted by Vault and the actual LUKS passphrase
being decrypted on demand by Vault (provided a successful login and proper permissions).
Create a new LVM Logical Volume for LUKS
Creating the LUKS-dedicated LVM Logical Volume (LV) / partition is straight-forward:
$ lvcreate -n lv.luks -L 10G vg.flex
# [output]
Logical volume "lv.luks" created.
Encrypt the LVM Logical Volume with LUKS
We may now encrypt the new Logical Volume using LUKS:
# Install LUKS utilities
$ apt-get install cryptsetup-bin
# Fill the new partition with random data
# (optional but recommended; takes time!)
$ dd if=/dev/urandom of=/dev/mapper/vg.flex-lv.luks bs=4k
# Initialize the LUKS partition
$ cat /etc/luks.passphrase \
| vault write -field=plaintext transit/decrypt/luks ciphertext=- \
| cryptsetup luksFormat --batch-mode --type luks2 --pbkdf argon2id --key-file - /dev/mapper/vg.flex-lv.luks
# Show the LUKS partition status
$ cryptsetup luksDump /dev/mapper/vg.flex-lv.luks
# [output (partial)]
LUKS header information
Version: 2
Epoch: 3
Metadata area: 16384 [bytes]
Keyslots area: 16744448 [bytes]
And “open” it:
# Open the LUKS partition
cat /etc/luks.passphrase \
| vault write -field=plaintext transit/decrypt/luks ciphertext=- \
| cryptsetup open --type luks --key-file - /dev/mapper/vg.flex-lv.luks luks
We can now format its filesystem (example given with ext4
):
$ mkfs.ext4 -L LUKS /dev/mapper/luks
# [output (partial)]
mke2fs 1.44.5 (15-Dec-2018)
Creating filesystem with 2617344 4k blocks and 655360 inodes
And mount it:
# Create the encrypted data mountpoint
mkdir -p /luks
# Mount the encrypted partition
mount /dev/mapper/luks /luks
# Show the partition capacity/usage
df -h /luks
# [output]
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/luks 9.8G 37M 9.3G 1% /luks
Mark /dev/mapper/luks
being the (encrypted) partition to use (not /dev/mapper/vg.flex-lv.luks
).
Boot and shutdown automation
In order to automate the various steps to open, mount, unmount and close the LUKS partition,
we will need a control script and systemd unit, which shall open and mount the LUKS partition
on start
, as well as unmount and close it on stop
.
Ideally, we would also use the Vault Agent to automatically authenticate
our compute instance using the Vault Exoscale Authentication Plugin
and act as proxy for the required vault
commands.
For the sake of simplicity, we’ll stick to “manually” authenticating with the Vault server, within the control script itself.
Control script and systemd unit
Let’s start with a small shell script to perform start
, stop
and status
actions;
in /usr/local/bin/luks-partition
:
#!/bin/sh
set -e
case "${1}" in
'start')
# Log into Vault
if ! test -e ~/.vault-token && [ -z "${VAULT_TOKEN}" ] && [ -z "${VAULT_AGENT_ADDR}" ]; then
VAULT_TOKEN="$(
vault write -field=token auth/exoscale/login \
role=storage \
instance=$(wget -qO- http://169.254.169.254/1.0/meta-data/instance-id) \
zone=$(wget -qO- http://169.254.169.254/1.0/meta-data/availability-zone)
)"
export VAULT_TOKEN
fi
# Open the LUKS partition
if ! test -e /dev/mapper/luks; then
cat /etc/luks.passphrase \
| vault write -field=plaintext transit/decrypt/luks ciphertext=- \
| cryptsetup open --type luks --key-file - /dev/mapper/vg.flex-lv.luks luks
fi
# Mount the LUKS partition
if ! mountpoint -q /luks; then
mkdir -p /luks
mount /dev/mapper/luks /luks
fi
;;
'stop')
# Unmount the LUKS partition
if mountpoint -q /luks; then
umount /luks
fi
# Close the LUKS partition
if test -e /dev/mapper/luks; then
cryptsetup close luks
fi
;;
'status')
mountpoint /luks
cryptsetup status luks
;;
esac
And the corresponding systemd unit; in /etc/systemd/system/luks-partition.service
:
[Unit]
Description=Start/stop the LUKS-encrypted partition
After=local-fs.target vault.service
[Service]
Type=oneshot
RemainAfterExit=yes
Environment=VAULT_ADDR=http://127.0.0.1:8200
ExecStart=/usr/local/bin/luks-partition start
ExecStop=/usr/local/bin/luks-partition stop
[Install]
WantedBy=multi-user.target
Which we can now enable and start:
# Reload systemd configuration
$ systemctl daemon-reload
# Enable the LUKS partition control unit
$ systemctl enable luks-partition
# Start the LUKS partition control unit
$ systemctl start luks-partition
# Query the LUKS partition control unit status
$ systemctl status luks-partition
# [output]
● luks-partition.service - Start/stop the LUKS-encrypted partition
Loaded: loaded (/etc/systemd/system/luks-partition.service; enabled; vendor preset: enabled)
Active: active (exited) since Fri 2021-07-02 07:07:28 UTC; 3s ago
Process: 21515 ExecStart=/usr/local/bin/luks-partition start (code=exited, status=0/SUCCESS)
Main PID: 21515 (code=exited, status=0/SUCCESS)
Should you have a service - e.g. MariaDB database - that has its data on the LUKS partition, just
make sure to set the proper After=luks-partition.service
dependency in its systemd unit or as
an override. Example given in /etc/systemd/system/mariadb.service.d/after-luks-partition.conf
:
[Unit]
After=luks-partition.service