Re-wrapping data after encryption key rotation
In addition to being able to store secrets, Vault can encrypt/decrypt data stored elsewhere. The primary use of this is to allow applications to encrypt their data while still storing it in their primary data store. Vault does not store the data.
The transit
secrets
engine handles
cryptographic functions on data-in-transit, and often referred to as Encryption
as a Service (EaaS). Both small amounts of arbitrary data, and large files such
as images, can be protected with the transit engine. This EaaS function can
augment or eliminate the need for Transparent Data Encryption (TDE) with
databases to encrypt the contents of a bucket, volume, and disk, etc.
Encryption Key Rotation
One of the benefits of using the Vault EaaS is its ability to rotate the
encryption keys. Keys can be rotated manually by a human, or an automated
process which invokes the key rotation API endpoint through cron
, a CI
pipeline, a periodic Nomad batch job, Kubernetes Job, etc.
The goal of this tutorial is to demonstrate an example for re-wrapping data after rotating an encryption key in the transit engine in Vault.
Personas
The end-to-end scenario described in this tutorial involves two personas:
- security engineer with privileged permissions to manage the encryption keys
- app with un-privileged permissions rewraps secrets via API
Challenge
Vault maintains the versioned keyring and the operator can decide the minimum version allowed for decryption operations. When data is encrypted using Vault, the resulting ciphertext is prepended with the version of the key used to encrypt it.
The following example shows data that was encrypted using the fourth version of a particular encryption key:
vault:v4:ueizdCqCJ/YhowQSvmJyucnLfIUMd4S/nLTpGTcz64HXoY69dwOrqerFzOlhqg==
For example, an organization could decide that a key should be rotated once a week, and that the minimum version allowed to decrypt records is the current version as well as the previous two versions. If the current version is five, then Vault would decrypt records that were sent to it with the following prefixes:
- vault:v5:lkjasfdlkjafdlkjsdflajsdf==
- vault:v4:asdfas9pirapirteradr33vvv==
- vault:v3:ouoiujarontoiue8987sdjf^1==
In this example, what would happen if you send Vault data that was encrypted
with the first or second version of the key (vault:v1:...
or vault:v2:...
)?
Vault would refuse to decrypt the data as the key used is less than the minimum key version allowed.
Solution
Luckily, Vault provides an easy way of re-wrapping encrypted data when a key is rotated. Using the rewrap API endpoint, a non-privileged Vault entity can send data encrypted with an older version of the key to have it re-encrypted with the latest version. The application performing the re-wrapping never interacts with the decrypted data. The process of rotating the encryption key and rewrapping records could (and should) be completely automated. Records could be updated slowly over time to lessen database load, or all at once at the time of rotation. The exact implementation will depend heavily on the needs of each particular organization or application.
Prerequisites
This lab was tested on macOS using an x86_64 based processor. If you are running macOS on an Apple silicon-based processor, use a x86_64 based Linux virtual machine in your preferred cloud provider.
Policy requirements
Each persona require a different set of capabilities. These are expressed in policies. If you are not familiar with policies, complete the policies tutorial.
The security engineer tasks require these capabilities.
# Manage the transit secrets enginepath "transit/keys/*" { capabilities = [ "create", "read", "update", "delete", "list", "sudo" ]}# Enable the transit secrets enginepath "sys/mounts/transit" { capabilities = [ "create", "update" ]}# Write ACL policiespath "sys/policies/acl/*" { capabilities = [ "create", "read", "update", "delete", "list" ]}# Create tokens for verification & testpath "auth/token/create" { capabilities = [ "create", "update", "sudo" ]}
Note
For these tasks, you can use Vault's root
token. However, it is
recommended that root tokens are only used for enough initial setup or in
emergencies. As a best practice, use tokens with an appropriate set of policies
based on your role in the organization.
The app tasks require these capabilities.
# rewrap_example.hclpath "transit/keys/my_app_key" { capabilities = ["read"]}path "transit/rewrap/my_app_key" { capabilities = ["update"]}# This last policy is needed to seed the database as part of the example.# It can be omitted if seeding is not requiredpath "transit/encrypt/my_app_key" { capabilities = ["update"]}
Lab setup
Clone GitHub repository
Download the sample application code from learn-vault-eaas-transit-rewrap repository to perform the steps described in this tutorial.
Clone the
learn-vault-eaas-transit-rewrap
repository.$ git clone git@github.com:hashicorp/learn-vault-eaas-transit-rewrap.git
Or download the repository:
This repository contains supporting content for this Vault tutorial.
Go into the
learn-vault-eaas-transit-rewrap
directory.$ cd learn-vault-eaas-transit-rewrap
Working directory
This tutorial assumes that the remainder of commands are executed within this directory.
Start Vault
In another terminal, start a Vault dev server with
root
as the root token.$ vault server -dev -dev-root-token-id root
The Vault dev server defaults to running at
127.0.0.1:8200
. The server is initialized and unsealed.Insecure operation
Do not run a Vault dev server in production. This approach starts a Vault server with an in-memory database and runs in an insecure way.
Switch back to the terminal you cloned the GitHub repository in. Export an environment variable for the
vault
CLI to address the Vault server.$ export VAULT_ADDR=http://127.0.0.1:8200
Export an environment variable for the
vault
CLI to authenticate with the Vault server.$ export VAULT_TOKEN=root
Note
For these tasks, you can use Vault's root token. However, it is recommended that root tokens are only used for enough initial setup or in emergencies. As a best practice, use an authentication method or token that meets the policy requirements.
The Vault server is ready.
Start MySQL database in Docker
The application requires a MySQL database. Docker provides a MySQL server image that satisfies the application's requirements
Note
For the demonstration, a MySQL database runs locally using Docker. However, these steps would work for an existing MySQL database by supplying the proper network information to your environment.
Pull a MySQL server image with
docker
.$ docker pull mysql/mysql-server:5.7
Create a directory for the demo data.
$ mkdir ~/rewrap-data
Create a database named
my_app
that sets the root user password toroot
and adds a user namedvault
.$ docker run --name mysql-rewrap \ --publish 3306:3306 \ --volume ~/rewrap-data:/var/lib/mysql \ --env MYSQL_ROOT_PASSWORD=root \ --env MYSQL_ROOT_HOST=% \ --env MYSQL_DATABASE=my_app \ --env MYSQL_USER=vault \ --env MYSQL_PASSWORD=vaultpw \ --detach mysql/mysql-server:5.7
The database is available.
Enable the transit secrets engine
(Persona: security engineer)
Enable the
transit
secrets engine.$ vault secrets enable transit
Create an encryption key to use for transit named
my_app_key
.$ vault write -f transit/keys/my_app_key
The transit key
my_app_key
is created.
Generate a new token for the sample app
(Persona: security engineer)
Before generating a token, create a limited scope policy named rewrap_example
for the sample application.
Review the limited scope policy stored in rewrap_example.hcl
.
$ cat rewrap_example.hcl# rewrap_example.hclpath "transit/keys/my_app_key" { capabilities = ["read"]}path "transit/rewrap/my_app_key" { capabilities = ["update"]}# This last policy is needed to seed the database as part of the example.# It can be omitted if seeding is not requiredpath "transit/encrypt/my_app_key" { capabilities = ["update"]}
Create the
rewrap_example
policy.$ vault policy write rewrap_example ./rewrap_example.hcl
The policy is created.
Create a token with the
rewrap_example
policy.$ vault token create -policy=rewrap_exampleKey Value--- -----token s.REGi86abx4psqc6OmmBw5IdTtoken_accessor nU7I3UEefRqLXAr89brO8f4Mtoken_duration 768htoken_renewable truetoken_policies ["default" "rewrap_example"]identity_policies []policies ["default" "rewrap_example"]
The output displays a token capable of using the transit key
my_app_key
.Create another token and store the token in the variable
APP_TOKEN
.$ APP_TOKEN=$(vault token create -format=json -policy=rewrap_example | jq -r ".auth.client_token")
Display the
APP_TOKEN
.$ echo $APP_TOKEN
The application uses this token to seed the database.
Run the sample application
(Persona: app)
The application stores data within the database. The data contains a field that require encryption. Vault provides that encryption through the transit secrets engine.
File | Description |
---|---|
Program.cs | Starting point of this sample app (the Main() method) is in this file. It reads the environment variable values, connects to Vault and the MySQL database. If the user_data table does not exist, it creates it. |
DBHelper.cs | Defines a method to create the user_data table if it does not exist. Finds and updates records that need to be rewrapped with the new key. |
AppDb.cs | Connects to the MySQL database. |
Record.cs | Sample data record template. |
VaultClient.cs | Defines methods necessary to rewrap transit data. |
WebHelper.cs | Helper code to seed the initial table schema. |
rewrap_example.csproj | Project file for this sample app. |
Run the sample application with the Vault server address, the transit key, and the generated token.
$ VAULT_TOKEN=$APP_TOKEN \ VAULT_ADDR=$VAULT_ADDR \ VAULT_TRANSIT_KEY=my_app_key \ SHOULD_SEED_USERS=true \ dotnet run
Example output:
Connecting to Vault server...Created (if not exist) my_app DBCreate (if not exist) user_data tableSeeded the database...Moving rewrap...Current Key Version: 1Found 0 records to rewrap.
The application finishes after it generates several database entries.
Connect to the database with the root credentials.
$ docker exec -it mysql-rewrap mysql -uroot -proot
Within the mysql shell, connect to the
my_app
table.$ CONNECT my_app;
Within the mysql shell, display 10 rows from the
user_data
table where the city starts with a Vault transit key.$ SELECT * FROM user_data WHERE city LIKE "vault:v1%" limit 10;+---------+-----------------+------------+-----------+-------------------------------------------------------------------+-------------+---------------+----------+---------------------------------------------------------------------------------------+| user_id | user_name | first_name | last_name | city | state | country | postcode | email |+---------+-----------------+------------+-----------+-------------------------------------------------------------------+-------------+---------------+----------+---------------------------------------------------------------------------------------+| 1 | sadfrog482 | Bella | Gonzalez | vault:v1:umJ81VPMsg8vhPVeNvdr2v1AM4EBPwHauwLEHhVidH/bXFyFbA== | Georgia | United States | 93220 | vault:v1:cvtg4xH/rOIPO2vSFmvrxSTMAKzC1dhr6zUF/WranhlL/6qNk9ifaz6dVEc5ZKATuvC1ftSQ || 2 | organictiger615 | Hugh | Richards | vault:v1:1Ov8PGK1itiJAdtvoV9CrYKzmwPQDFUZAyPKYG8luhxqRP/iOLKI | Minnesota | United States | 13652 | vault:v1:8gPJgOQB4/bq36IA6291mpG3szbNR6GxbWQylwJX8UlZXvNWr849nESyYAwBx2YtanJXFio= || 3 | orangefish367 | Grace | Stephens | vault:v1:HMqtwuqH7LOjpBcUbn1pRC/9EYEIRQu0frUX/ROldnTK | Alaska | United States | 48923 | vault:v1:jYmJLyYwMeti3aqQ3+QjvJWyg5MEnlY5BV/d5gIoMf+Uj1LyKOByh+6u/4t6ybP1h2CWRZn0 || 4 | bluekoala754 | Stacy | Grant | vault:v1:IzjRTrwX8zqgCCM2ZB86/T+tI9d4A1EI4IVUH9O86D2M0PdrsC0= | Connecticut | United States | 97712 | vault:v1:E4gBEE2jh0ohYMjZu2fCABy0IG7MB3Jr3Jm4KIU5c/hub4sALyYahIV9nAqVXFFRRw/f || 5 | silverfrog747 | Salvador | Barrett | vault:v1:FLPCNxuJn9J5nnctKFROCwlcWaw/Yvq/y1Y5buefSytGExIglVGnMmQ= | Washington | United States | 26448 | vault:v1:QBra8YwJ1d1Lxu9TukU8qTe9PjxEqUff0fAPzDdSMU/1c/yNC7LNIJx0BsYoJoszcC+vnVyo1no= || 6 | organicfrog759 | Gavin | Price | vault:v1:LOJAibVMBbagasMkCvmpdd1ss5R7LTPkShBMfvh/kDye7oxa | Minnesota | United States | 25360 | vault:v1:2rY9H15ejMXdumgpOxJXgjXbwoN9U8gk/+Xk+2otKhVV/23zTvxfDctxAXWUXOvsqcsM || 7 | smallostrich963 | Kurt | May | vault:v1:CYLkwI2lIQ/dYcMEUhd/cwAxwwFXwS8/k//KSUu+xIQh2kDp | Minnesota | United States | 97820 | vault:v1:uC8rNyb8ju++0bhC6jQdYIct7j2OYeX58TqsVMmI/e7aNL2Oe76ruJ1IVgSk7u2a || 8 | lazyostrich623 | Kristen | King | vault:v1:DsafGtaKOeM6qeruDB2Tl6YcV1xjgjsi3HyDigUVFYjzbmI= | Mississippi | United States | 28583 | vault:v1:01ZXSUMIJUFx2u7ftgl+8tsfNmU1x89h9i5DUY+SiJ2vry+6ne8O0gELG4wE50XZzide3Q== || 9 | angryzebra522 | Robert | Williams | vault:v1:+I4mFByIoEzQs2ZqYTK/OlsT14S1bo5F6cpRVukBNxhr04jAQ/BU | Alaska | United States | 90033 | vault:v1:lszAwKpWp6ij+dILeybir/Enoge3ArsIUh1bHNl+CqHn2RzhcTNJrr6ZoqG8YQvxQWJ+DiU9Qw== || 10 | blueleopard693 | Morris | Rivera | vault:v1:vuy1qNHQdLjaCB0tqUOCv3NMbr0Leep0B/5pXgpdGJw= | Alabama | United States | 11206 | vault:v1:qSuvWlyb6Ik/gY1t+Z23BBfT8KbVN6RxehOfSRDop9fymAS25X9BH3ljLUvmmow6cusqpyw= |+---------+-----------------+------------+-----------+-------------------------------------------------------------------+-------------+---------------+----------+---------------------------------------------------------------------------------------+10 rows in set (0.00 sec)
The results display 10 rows that match this query. The city field is encrypted with the Vault transit key.
Drop the connection to the database.
$ exit
The application used the Vault server through the application token to encrypt these fields.
Rotate the encryption key
(Persona: security engineer)
The encryption key my_app_key
can be rotated.
Rotate the
my_app_key
transit key.$ vault write -f transit/keys/my_app_key/rotateSuccess! Data written to: transit/keys/my_app_key/rotate
Display information about the
my_app_key
transit key.$ vault read transit/keys/my_app_keyKey Value--- -----allow_plaintext_backup falsedeletion_allowed falsederived falseexportable falsekeys map[1:1543528755 2:1543528986]latest_version 2min_available_version 0min_decryption_version 1min_encryption_version 0name my_app_keysupports_decryption truesupports_derivation truesupports_encryption truesupports_signing falsetype aes256-gcm96
The output displays that this transit key has two versions.
Programmatically re-wrap the data
(Persona: app)
The application's database contains fields encrypted with the first version of the transit key.
Run the application again to re-wrap the encrypted fields.
$ VAULT_TOKEN=$APP_TOKEN \ VAULT_ADDR=$VAULT_ADDR \ VAULT_TRANSIT_KEY=my_app_key \ SHOULD_SEED_USERS=true \ dotnet run
Example output:
Connecting to Vault server...Created (if not exist) my_app DBCreate (if not exist) user_data tableSeeded the database...Current Key Version: 2Found 3500 records to rewrap.Wrapped another 10 records: 10 so far...Wrapped another 10 records: 20 so far...Wrapped another 10 records: 30 so far......
The application finishes after it re-wraps the encrypted fields in the existing entries.
Connect to the database with the root credentials.
$ docker exec -it mysql-rewrap mysql -uroot -proot
Within the mysql shell, connect to the
my_app
table.$ CONNECT my_app;
Within the mysql shell, display 10 rows from the
user_data
table where the city starts with a Vault transit key versionv1
.$ SELECT * FROM user_data WHERE city LIKE "vault:v1%" limit 10;Empty set (0.00 sec)
The results of this query are an empty set.
Within the mysql shell, display 10 rows from the
user_data
table where the city starts with a Vault transit key versionv2
.$ SELECT * FROM user_data WHERE city LIKE "vault:v2%" limit 10;+---------+---------------+------------+-----------+-----------------------------------------------------------------------+---------------+---------------+----------+---------------------------------------------------------------------------------------+| user_id | user_name | first_name | last_name | city | state | country | postcode | email |+---------+---------------+------------+-----------+-----------------------------------------------------------------------+---------------+---------------+----------+---------------------------------------------------------------------------------------+| 1 | sadbird321 | Steven | Armstrong | vault:v2:ImEDuJXesH0dN7pWwgTQ1Fnhv/w8HrA9lN4o3ct7+qj0SRNXMfc= | Utah | United States | 48605 | vault:v2:HDFbwc365OALVRd+jEIGL4leGIAqAWBBTnvXnBxiLiJx/yUoJxU5FdnllIClNzFoqTbuqoNugJ0= || 2 | lazycat275 | Leah | Mendoza | vault:v2:fUK7ca8n/Ji2WfZJR+2YgDI0aQf3+7kIKBLUnOqoyjEQ2NSRWg== | Iowa | United States | 54102 | vault:v2:HqkEwJqsGbmFstQ7vRriWf3qrgLGgIgdmS4ubIti079xILbOvbi6qGJkLFBj6H3ulwzmWQ== || 3 | bigwolf912 | Dean | Henry | vault:v2:/mpTDv+Q/NuAfJlS0oeUIXIOfgztr9GapAGsW2m2iXEZoE5nnw== | Ohio | United States | 49932 | vault:v2:qbGXgUj4SYPNsXF8jG5jCIhh6SJOFbDFqaBSV3hMB+B7k0UQMxkVlBTOnYgo3rmVh2M= |...10 rows in set (0.00 sec)
The results display 10 rows that match this query. The city field is encrypted with the second version of the Vault transit key.
Drop the connection to the database.
$ exit
Clean up
Stop and remove the MySQL container.
$ docker rm mysql-rewrap --force
Remove the
rewrap-data
directory$ rm -rf ~/rewrap-data
Remove the local git repository
$ cd .. && rm -rf learn-vault-eaas-transit-rewrap
Conclusion
An application similar to this could be scheduled via cron, run periodically as a Nomad batch job, or executed in a variety of other ways. You could also modify it to re-wrap a limited number of records at a time so as to not put undue strain on the database. The final implementation should be based upon the needs and design goals specific to each organization or application.