--- layout: guides page_title: Transit Secrets Re-wrapping - Guides description: >- The goal of this guide is to demonstrate one possible way to re-wrap data after rotating an encryption key in the transit engine in Vault. --- # Transit Secrets Engine In addition to being able to store secrets, Vault can encrypt/decrypt data that is 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` secret engine](/docs/secrets/transit) 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 as a Service](/img/vault-encryption.png) ## Encryption Key Rotation One of the benefits of using the Vault EaaS is its ability to easily 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 guide is to demonstrate an example for re-wrapping data after rotating an encryption key in the transit engine in Vault. ## Reference Material - [Encryption as a Service](/guides/encryption/transit) - [Transit Secret Engine](/docs/secrets/transit) - [Transit Secret Engine API](/api/secret/transit) - [Transparent Data Encryption in the Modern Datacenter](https://www.hashicorp.com/blog/transparent-data-encryption-in-the-modern-datacenter) ## Estimated Time to Complete 30 minutes ## Personas The end-to-end scenario described in this guide 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 To perform the tasks described in this guide, you need to have a Vault environment. Refer to the [Getting Started](/intro/getting-started/install) guide to install Vault. Make sure that your Vault server has been [initialized and unsealed](/intro/getting-started/deploy). The following tools are required in order to successfully run the sample application provided in this guide: - [.NET Core](https://www.microsoft.com/net/download) - [Docker](https://docs.docker.com/install/) Download the sample application code from [vault-guides](https://github.com/hashicorp/vault-guides/tree/master/encryption/vault-transit-rewrap) repository to perform the steps described in this guide. The `vault-transit-rewrap-example` contains the following: ```bash . ├── AppDb.cs ├── DBHelper.cs ├── Program.cs ├── README.md ├── Record.cs ├── VaultClient.cs ├── WebHelper.cs └── rewrap_example.csproj ``` ### Policy requirements -> **NOTE:** For the purpose of this guide, you can use the **`root`** token to work with Vault. However, it is recommended that root tokens are only used for just 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. To perform all tasks demonstrated in this guide, your policy must include the following permissions: ```shell # Manage transit secret engine path "transit/keys/*" { capabilities = [ "create", "read", "update", "delete", "list", "sudo" ] } # Enable transit secret engine path "sys/mounts/transit" { capabilities = [ "create", "update" ] } # Write ACL policies path "sys/policy/*" { capabilities = [ "create", "read", "update", "delete", "list" ] } # Create tokens for verification & test path "auth/token/create" { capabilities = [ "create", "update", "sudo" ] } ``` If you are not familiar with policies, complete the [policies](/guides/identity/policies) guide. ## Steps This guide introduces a sample _.Net_ application which automates the re-wrapping of the data using the latest encryption key. For the purpose of this guide, 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. You are going to perform the following steps: 1. [Test database setup (Docker)](#step1) 1. [Enable the transit secret engine](#step2) 1. [Generate a new token for sample app](#step3) 1. [Run the sample application](#step4) 1. [Rotate the encryption keys](#step5) 1. [Re-wrapping data programmatically](#step6) ### Step 1: Test database setup (Docker) ((#step1)) You need a database to test with. You can create one to test with easily using Docker: ```bash # Pull the latest mysql container image docker pull mysql/mysql-server:5.7 # Create a directory for our data (change the following line if running on Windows) mkdir ~/rewrap-data # Run the container. The following command creates a database named 'my_app', # specifies the root user password as 'root', and adds a user named vault docker run --name mysql-rewrap \ -p 3306:3306 \ -v ~/rewrap-data/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=root \ -e MYSQL_ROOT_HOST=% \ -e MYSQL_DATABASE=my_app \ -e MYSQL_USER=vault \ -e MYSQL_PASSWORD=vaultpw \ -d mysql/mysql-server:5.7 ``` ### Step 2: Enable the transit secret engine ((#step2)) (**Persona:** security engineer) #### CLI command Enable the `transit` secret engine by executing the following command: ```shell-session $ vault secrets enable transit ``` Create an encryption key to use for transit named, "my_app_key". ```shell-session $ vault write -f transit/keys/my_app_key ``` #### API call using cURL Enable the `transit` secret engine via API, use the `/sys/mounts` endpoint: ```shell-session $ curl --header "X-Vault-Token: " \ --request POST \ --data \ /v1/sys/mounts/transit ``` Where `` is your valid token, and `` holds [configuration parameters](/api/system/mounts#enable-secrets-engine) of the secret engine. To crate a new encryption key, use the `transit/keys/` endpoint: ```shell-session $ curl --header "X-Vault-Token: " \ --request POST \ --data \ /v1/transit/keys/ ``` Where `` holds [configuration parameters](/api/secret/transit#create-key) to specify the key type. **Example:** ```shell-session $ curl --header "X-Vault-Token: ..." \ --request POST \ --data '{"type": "transit"}' \ https://localhost:8200/v1/sys/mounts/transit ``` The above example passes the **type** (`transit`) in the request payload which at the `sys/mounts/transit` endpoint. Next, create an encryption key to use for transit named, "my_app_key". ```shell-session $ curl --header "X-Vault-Token: ..." \ --request POST \ https://localhost:8200/v1/transit/keys/my_app_key ``` ### Step 3: Generate a new token for sample app ((#step3)) (**Persona:** security engineer) Before generating a token, create a limited scope policy named, "**rewrap_example**" for the sample application. The ACL policy (`rewrap_example.hcl`) looks as follows: ```shell path "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 required path "transit/encrypt/my_app_key" { capabilities = ["update"] } ``` #### CLI command Create `rewrap_example` policy: ```shell-session $ vault policy write rewrap_example ./rewrap_example.hcl ``` Finally, create a token to use the `rewrap_example` policy: ```shell-session $ vault token create -policy=rewrap_example ``` **Example:** ```shell-session $ vault token create -policy=rewrap_example Key Value --- ----- token 68396128-82d8-002e-f289-1106944fee9f token_accessor 75f05f43-6a5f-2eb1-5bb8-0de3c7cf0996 token_duration 768h token_renewable true token_policies [default rewrap_example] ``` The generated token is what the sample application uses to connect to Vault. #### API call using cURL To create a policy via API, use the `/sys/policy` endpoint: ```shell-session $ curl --request PUT --header "X-Vault-Token: ..." \ --data @payload.json \ https://localhost:8200/v1/sys/policy/rewrap_example $ cat payload.json { "policy": "path \"transit/keys/my_app_key\" { capabilities = [\"read\"] } path \"transit/rewrap/my_app_key\" ... }" } ``` Finally, create a token to use the `rewrap_example` policy: ```shell-session $ curl --header "X-Vault-Token: ..." --request POST \ --data '{ "policies": ["rewrap_example"] }' \ https://localhost:8200/v1/auth/token/create | jq { "request_id": "da8bde73-99ab-b435-a344-fb963b3a599f", "lease_id": "", "renewable": false, "lease_duration": 0, "data": null, "wrap_info": null, "warnings": null, "auth": { "client_token": "997107d5-9049-8b4b-8f39-a33a458fd02d", "accessor": "d400076b-4143-4d63-7473-a8cc52c73ba3", "policies": [ "default", "rewrap-example" ], "metadata": null, "lease_duration": 2764800, "renewable": true, "entity_id": "" } } ``` The generated token is what the sample application uses to connect to Vault. ### Step 4: Run the sample application ((#step4)) (**Persona:** app) You are now ready to run the app. Be sure to [download](#prerequisites) the sample application code before beginning. **Sample application** | 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. | The sample app retrieves the user token, Vault address, and the name of the transit key through environment variables. Be sure to supply the token created in [Step 3](#step3): ```shell-session $ VAULT_TOKEN= \ VAULT_ADDR= \ VAULT_TRANSIT_KEY=my_app_key \ SHOULD_SEED_USERS=true \ dotnet run ``` > If you need to seed test data you can do so by including the > `SHOULD_SEED_USERS=true`. **Example:** ```shell-session $ VAULT_TOKEN=$TOKEN VAULT_ADDR=http://localhost:8200 VAULT_TRANSIT_KEY=my_app_key SHOULD_SEED_USERS=true dotnet run Connecting to Vault server... Created (if not exist) my_app DB Create (if not exist) user_data table Seeded the database... Moving rewrap... Current Key Version: 5 Found 0 records to rewrap. ``` You can inspect the contents of the database with: ```shell-session $ docker exec -it mysql-rewrap mysql -uroot -proot ... mysql> DESC user_data; mysql> SELECT * FROM user_data WHERE dob LIKE "vault:v1%" limit 10; ...data... ``` ### Step 5: Rotate the encryption keys ((#step5)) (**Persona:** security engineer) The encryption key (`my_app_key`) can be rotated easily. #### CLI command To rotate the key, you write to the `transit/keys//rotate` path. ```shell-session $ vault write -f transit/keys/my_app_key/rotate Success! Data written to: transit/keys/my_app_key/rotate ``` Run the command a few times to generate several versions of the encryption key for testing. To view the key information: ```shell-session $ vault read transit/keys/my_app_key Key Value --- ----- allow_plaintext_backup false deletion_allowed false derived false exportable false keys map[5:1519623974 6:1519623980 1:1519620952 2:1519623255 3:1519623285 4:1519623603] latest_version 6 min_decryption_version 1 min_encryption_version 0 name my_app_key supports_decryption true supports_derivation true supports_encryption true supports_signing false type aes256-gcm96 ``` You can see that in the above example the current version of the key is six. There is no restriction about a minimum encryption key version, and any of the key versions can decrypt the data (`min_decryption_version`). Let's enforce the use of the encryption key at version five or later to decrypt data. ```shell # replace '5' with the appropriate version $ vault write transit/keys/my_app_key/config min_decryption_version=5 # Verify the changes were successful $ vault read transit/keys/my_app_key Key Value --- ----- allow_plaintext_backup false deletion_allowed false derived false exportable false keys map[5:1519623974 6:1519623980] latest_version 6 min_decryption_version 5 min_encryption_version 0 name my_app_key supports_decryption true supports_derivation true supports_encryption true supports_signing false type aes256-gcm96 ``` #### API call using cURL To rotate the encryption key via API, use the `transit/keys//rotate` endpoint: ```shell-session $ curl --request POST --header "X-Vault-Token: ..." \ https://localhost:8200/v1/transit/keys/my_app_key/rotate ``` Run the command a few times to generate several versions of the encryption key for testing. ```shell # Verify the changes were successful $ curl --request GET --header "X-Vault-Token: ..." \ https://localhost:8200/v1/transit/keys/my_app_key | jq { "request_id": "ed13436a-4816-2f51-0552-6a001e823548", "lease_id": "", "renewable": false, "lease_duration": 0, "data": { "allow_plaintext_backup": false, "deletion_allowed": false, "derived": false, "exportable": false, "keys": { "1": 1519620952, "2": 1519623255, "3": 1519623285, "4": 1519623603, "5": 1519623974, "6": 1519623980 }, "latest_version": 6, "min_decryption_version": 1, "min_encryption_version": 0, "name": "my_app_key", ... }, ... } ``` You can see that in the above example the current version of the key is six. There is no restriction about the minimum encryption key version, and any of the key versions can decrypt the data (`min_decryption_version`). Let's enforce the use of the encryption key at version five or later to decrypt the data. ```shell-session $ curl --request POST --header "X-Vault-Token: ..." \ --data '{ "min_decryption_version": 5 }' https://localhost:8200/v1/transit/keys/my_app_key/config # Verify the changes were successful $ curl --request GET --header "X-Vault-Token: ..." \ https://localhost:8200/v1/transit/keys/my_app_key | jq { ... "data": { ... "keys": { "5": 1519623974, "6": 1519623980 }, "latest_version": 6, "min_decryption_version": 5, "min_encryption_version": 0, "name": "my_app_key", ... }, } ``` ### Step 6: Programmatically re-wrap the data ((#step6)) (**Persona:** app) Now you have records in the database and you have updated our minimum key version. You can run the application again and should see it update records as appropriate. Remember you can inspect records using the MySQL shell (see above). **Example:** ```shell-session $ VAULT_TOKEN=2616214b-6868-3589-b443-0330d7915882 VAULT_ADDR=http://localhost:8200 \ VAULT_TRANSIT_KEY=my_app_key SHOULD_SEED_USERS=true dotnet run Connecting to Vault server... Created (if not exist) my_app DB Create (if not exist) user_data table Seeded the database... Current Key Version: 6 Found 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... ... ``` #### Validation The application has now re-wrapped all records with the latest key. You can verify this by running the application again, or by inspecting the records using the MySQL client. ```shell-session $ docker exec -it mysql-rewrap mysql -uroot -proot ... mysql> DESC user_data; mysql> SELECT * FROM user_data WHERE dob LIKE "vault:v1%" limit 10; Empty set (0.00 sec) mysql> SELECT * FROM user_data WHERE dob LIKE "vault:v6%" limit 10; ...data... ``` ### Conclusion An application similar to this could be scheduled via cron, run periodically as a [Nomad batch job](https://www.nomadproject.io/docs/job-specification/periodic), 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. ## Next Steps Since the main focus of this guide was to programmatically rewrap your secrets using the latest encryption key, the token used by the sample application was generated manually. In a production environment, you'll want to pass the token in a more secure manner. Refer to the [Cubbyhole Response Wrapping](/guides/secret-mgmt/cubbyhole) guide to wrap the token so that only the expecting app can unwrap to obtain the token. Also, refer to the [AppRole Pull Authentication](/guides/identity/authentication) to generate tokens for apps using the AppRole auth method.