ec620a7765
* implement mdx remote * fix an unfenced code block * fix partials path Co-authored-by: Jim Kalafut <jkalafut@hashicorp.com>
641 lines
21 KiB
Plaintext
641 lines
21 KiB
Plaintext
---
|
|
layout: guides
|
|
page_title: Transit Secrets Re-wrapping - Guides
|
|
sidebar_title: Transit Secrets Re-wrapping
|
|
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: <TOKEN>" \
|
|
--request POST \
|
|
--data <PARAMETERS> \
|
|
<VAULT_ADDRESS>/v1/sys/mounts/transit
|
|
```
|
|
|
|
Where `<TOKEN>` is your valid token, and `<PARAMETERS>` holds [configuration
|
|
parameters](/api/system/mounts#enable-secrets-engine) of the secret engine.
|
|
|
|
To crate a new encryption key, use the `transit/keys/<key_name>` endpoint:
|
|
|
|
```shell-session
|
|
$ curl --header "X-Vault-Token: <TOKEN>" \
|
|
--request POST \
|
|
--data <PARAMETERS> \
|
|
<VAULT_ADDRESS>/v1/transit/keys/<KEY_NAME>
|
|
```
|
|
|
|
Where `<PARAMETERS>` 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=<APP_TOKEN> \
|
|
VAULT_ADDR=<VAULT_ADDRESS> \
|
|
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/<KEY_NAME>/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/<KEY_NAME>/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.
|