HashiCorp Vault Operations Professional exam practice guide — Part 2

Transit secrets engine & auto-unseal.

Glen Yu
6 min readFeb 10, 2024

Transit secrets engine

The transit secrets engine is your “encryption as a service”, so let us enable it first:

vault secrets enable -path tokenizer transit

The -path is optional, but is a good way to keep your secrets engines organized. If you do not provide a path, the default is to use the same name as the secrets engine.

I will then create my encryption key:

vault write -f tokenizer/keys/mykey

Before data can be encrypted, it needs to first be base64 encoded and passed in as “plaintext”:

vault write tokenizer/encrypt/mykey plaintext=$(base64 <<< 'encryptme!!')

The output look similar to the following:

Key            Value
--- -----
ciphertext vault:v1:QDbZXSsPb/Iie6Y4snrw2tGE+gyAXax3lMLkyFZuT3jkxAjy90yQjw==
key_version 1

Decryption work similarly to encryption, except you call the /decrypt endpoint and pass in the ciphertext instead:

vault write tokenizer/decrypt/mykey ciphertext='vault:v1:QDbZXSsPb/Iie6Y4snrw2tGE+gyAXax3lMLkyFZuT3jkxAjy90yQjw=='

The plaintext returned to you will have to be base64 decoded to get your original data back.

**IMPORTANT**: it is important to note that Vault does NOT store your encrypted ciphertexts. It is your responsibility to provide store for them so that you may retrieve and decrypt at a later time.

Rotate

You can rotate encryption keys by simply calling the /rotate endpoint for the key:

vault write -f tokenizer/keys/mykey/rotate

If you were to encrypt the same data again, you will get back a ciphertext which will have a prefix of vault:v2 now (instead of vault:v1). This identifies that the message was encrypted by Vault and more importantly, the key version that was used.

Rewrap

Instead of re-encrypting the original data with the new, rotated key, you can just rewrap your previous version’s ciphertext with the new key instead:

vault write tokenizer/rewrap/mykey ciphertext='vault:v1:QDbZXSsPb/Iie6Y4snrw2tGE+gyAXax3lMLkyFZuT3jkxAjy90yQjw=='

NOTE: rewrapping a previous version’s ciphertext may not yield the same ciphertext as encrypting the orignal data with the new key. However, they both will decrypt to the same plaintext.

Invalidate

When you rotate a key in the transit secrets engine, you are not truly rotating it in the way you might expect. The older versions of the keys still exists and are actually still active until you tell it otherwise! If you think about it, it makes sense. What would happen to your existing secrets wrapped with the old key if it becomes invalidated when you rotate keys?

Once you have rewrapped your old ciphertexts and are ready to remove your old keys, you can update your key’s config to specify the min_available_version, min_decryption_version, and min_encryption_version accordingly. I will not be showing you an example of this here, but instead posing it to you as a challenge (see challenge #2 below) to search the documentation and figure it out on your own.

Auto-unseal

Auto-unsealer Vault instance

I will be making the Vault server created in part 1 of this guide the auto-unsealer. To do this we will enable a transit secrets engine and create a key for auto-unseal purposes:

vault secrets enable transit

vault write -f transit/keys/autounseal

I will then create a Vault policy that gives the permissions to encrypt and decrypt with said key by applying the policy defined below:

vault policy write autounseal ./autounseal-policy.hcl

TIP: it is a good idea to name your policy after the policy definition file

Finally, we will create ourselves a token that has the permissions to use this key:

vault token create -orphan -policy="autounseal"

Do not worry if you do not fully understand this part regarding policies and tokens. I will dive deeper into this topic in part 3 of my guide. For now, keep the token this command generated handy as you will need it for the next step…

Vault server instance

Spin yourself up a new Vault instance because *this* is going to be the actual Vault server you will be interfacing with for the rest of the guide. However, do NOT start or initialize this server just yet as we still need to modify the configuration file so that it knows what server (or service) to look towards for auto-unsealing. My config is below:

In the seal stanza, we defined that it will be using the transit secrets engine and the address of that Vault server instance. The path to the encryption key and the token which we generated with the appropriate permissions is also provided here.

NOTE: my auto-unsealer Vault instance is accessed with an HTTPS endpoint because it was enabled in part 1’s challenge

You can now start and initialize Vault:

export VAULT_ADDR='http://127.0.0.1:8200'

vault operator init -recovery-shares=1 -recovery-threshold=1

Which produces the following output:

Recovery Key 1: KRIBbiPw86+8POMu+WklaZHowFrwMkMUXk5eM2CKrW0=

Initial Root Token: hvs.maMFrnbioy3ywtGrUeJw6K85

Success! Vault is initialized

Did you notice that I specified -recovery-shares and -recovery-threshold instead of -key-shares and -key-threshold, respectively? Also, initialization returns a Recovery Key instead of an Unseal Key. I am not going to tell you the difference between them. That is for you to read up on.

CHALLENGE #1: create a 4096-bit RSA key called “myrsa” under the tokenizer/ path

CHALLENGE #2: rotate key, “mykey” once more but keep only the last 2 versions of the keys active and ensure key v1 is deleted

CHALLENGE #3: using v2 of “mykey”, encrypt a message, rewrap it with v3 (newest) and decrypt it again

CHALLENGE #4: configure auto-unseal using a KMS from a cloud provider

>> SPOILER ALERT!!! SOLUTION GUIDE BELOW!!! <<

Challenge solutions guide

SOLUTION #1

vault write -f tokenizer/keys/myrsakey type=rsa-4096

The default key type is AES256-GCM96, but many types are supported, including the popular RSA, ECDSA and HMAC.

Different key types also have different features. For example: AES-GCM keys do not support signing, but RSA keys do (but in turn, they do not support key derivation).

SOLUTION #2

If you have been following along with the example I provided in the guide, you should have 2 versions of mykey (you can confirm this with vault read tokenizer/keys/mykey). To rotate it once more, simply run vault write -f tokenizer/keys/mykey/rotate (your latest key version should be v3). To keep the last 2 versions the key active, write to the key’s config and set the min_decryption_version and min_encryption_version to 2 thus allowing v2 and v3 to be used:

vault write -f tokenizer/keys/mykey/config \
min_decryption_version=2 \
min_encryption_version=2

If you tried to decrypt any ciphertexts that uses the v1 key now, you will get an error telling you that this is “disallowed by policy (too old)”.

The next ask is for the the v1 key to be deleted. For this, you need to set the min_available_version. Any key lower than what is specified for this field gets deleted. As long as a key is available, you can always update the min_decryption_version and min_encryption_version and still be able to use the key, but once it is deleted, it is gone.

SOLUTION #3

As long as it is available and allowed, you can still specify a previous version of a key to be used for encrypting messages (you do not need to specify for decrypting because this information is already embedded in the prefx of your ciphertext):

vault write tokenizer/encrypt/mykey key_version=2 plaintext=$(base64 <<< 'my secret msg')

vault write tokenizer/rewrap/mykey ciphertext='[CIPHERTEXT_v2]'

vault write tokenizer/decrypt/mykey ciphertext='[CIPHERTEXT_V3]'

SOLUTION #4

I will not be posting a solution for this challenge per se. I have multiple GitHub repos that deploy Vault on the various cloud provider’s serverless container service and they all use the provider’s native KMS for auto-unseal, so you can check those out and adapt it for the VM solution. You just have to make sure the VM’s default service account has the correct IAM permissions and scopes.

NOTE: you will see that I use a fairly blank Vault config in these repos as many of the settings are passed at run time via environment variables. This is actually the recommended way to run Vault.

--

--

Glen Yu

Cloud Engineering @ PwC Canada. I'm a Google Cloud GDE, HashiCorp Ambassador and HashiCorp Core Contributor (Nomad). Also an ML/AI enthusiast!