Vault
Codify Vault Enterprise management with Terraform
Personas
The scenario described in this tutorial introduces the following personas:
admin
is the organization-level administratorstudent
is a user allowed to write data to a path in vault
Challenge
A manual system administration can become a challenge as the scale of infrastructure increases. Often, an organization must manage multiple Vault environments (development, testing, staging, production, etc.). Keeping up with the increasing management demand soon becomes a challenge without some sort of automation.
Solution
One of the pillars behind the Tao of HashiCorp is automation through codification.
HashiCorp Terraform is an infrastructure as code which enables the operation team to codify the Vault configuration tasks such as the creation of policies. Automation through codification allows operators to increase their productivity, move quicker, promote repeatable processes, and reduce human error.
This tutorial demonstrates techniques for creating Vault policies and configurations using Terraform Vault provider.
Prerequisites
Enterprise Only
This tutorial creates namespaces which require Vault Enterprise Standard license.
If you are running open-source Vault, see the Codify Management of Vault Using Terraform tutorial.
Scenario introduction
Vault administrators must manage multiple Vault environments. The test servers Vault administrators, at the end of each test cycle destroy the test servers and provision a new set of servers for the next test cycle. To automate the Vault server configuration, you are going to use Terraform to provision the following Vault resources.
Type | Name | Description |
---|---|---|
namespace | finance | A namespace dedicated to the finance organization |
namespace | engineering | A namespace dedicated to the engineering organization |
namespace | education | A namespace dedicated to the education organization |
namespace | training | A child-namespace under education dedicated to the training team |
namespace | vault_cloud | A child-namespace under education/training dedicated to the vault_cloud team |
namespace | engineering | A child-namespace under education/training dedicated to the Boundary team |
ACL Policy | admins | Sets policies for the admin team |
ACL Policy | fpe-client | Sets policies for clients to encode/decode data through transform secrets engine |
auth method | userpass | Enable and create a user, "student" with admins and fpe-client policies |
auth method | approle | Enable approle auth method in the education/training and create a test-role role |
secrets engine | kv-v2 | Enable kv-v2 secrets engine in the finance namespace |
secrets engine | transform | Enable transform secrets engine at transform |
transformation | ccn-fpe | Transformation to perform format preserving encryption (FPE) transformation on credit card numbers |
transformation template | ccn | Define the data format structure for credit card numbers |
alphabet | numerics | Set of allowed characters |
You need to create the admins
policy in all namespaces: root
, finance
, and
engineering
. The expected admin tasks are the same across the namespaces.
Note
Transform secrets engine requires Vault Enterprise Advanced Data Protection (ADP) license.
Examine the Terraform files
Clone or download the demo assets from the hashicorp/learn-vault-codify GitHub repository to perform the steps described in this tutorial.
$ git clone https://github.com/hashicorp-education/learn-vault-codify
Change the working directory to
learn-vault-codify/enterprise
.$ cd learn-vault-codify/enterprise
The directory contains Terraform files to configure Vault.
. ├── auth.tf ├── main.tf ├── policies │ ├── admin-policy.hcl │ └── fpe-client-policy.hcl ├── policies.tf ├── provider.tf ├── secrets.tf
Review provider.tf
Open the provider.tf
file in your preferred text editor to examine its content.
provider.tf
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
#------------------------------------------------------------------------------
# To configure Transform secrets engine, you need vault provider v2.12.0 or later
#------------------------------------------------------------------------------
terraform {
required_providers {
vault = {
source = "hashicorp/vault"
version = "~> 5.0.0"
}
}
}
#------------------------------------------------------------------------------
# To leverage more than one namespace, define a vault provider per namespace
#------------------------------------------------------------------------------
provider "vault" {
# configuration options
}
Each Terraform module must declare which providers it requires, so that Terraform can install and use them. Provider requirements are declared in a required_providers
block.
The terraform
block in the provider.tf
file declares that this module requires the vault
provider. The source
argument specifies the provider's source address, which is hashicorp/vault
. The version
argument specifies the version of the provider to use.
In this instance, the vault
provider specifies a version constraint of ~> 5.0.0
, which means any version from 5.0.0 up to but not including 6.0.0.
The provider
block for vault
instructs Terraform to interact with the Vault API and requires a address
and token
. Terraform looks for the VAULT_ADDR
and VAULT_TOKEN
environment variables to set these. See Vault Terraform provider documentation for more information.
Review main.tf
Open the main.tf
file in your preferred text editor to examine its content.
main.tf
#------------------------------------------------------------------------------
# Create namespaces: finance, and engineering
#------------------------------------------------------------------------------
resource "vault_namespace" "finance" {
path = "finance"
}
resource "vault_namespace" "engineering" {
path = "engineering"
}
#---------------------------------------------------------------
# Create nested namespaces
# education has childnamespace, 'training'
# training has childnamespace, 'secure'
# secure has childnamespace, 'vault_cloud' and 'boundary'
#---------------------------------------------------------------
resource "vault_namespace" "education" {
path = "education"
}
# Create a childnamespace, 'training' under 'education'
resource "vault_namespace" "training" {
namespace = vault_namespace.education.path
path = "training"
}
# Create a childnamespace, 'vault_cloud' and 'boundary' under 'education/training'
resource "vault_namespace" "vault_cloud" {
namespace = vault_namespace.training.path_fq
path = "vault_cloud"
}
# Create 'education/training/boundary' namespace
resource "vault_namespace" "boundary" {
namespace = vault_namespace.training.path_fq
path = "boundary"
}
# create a kv v2 secrets engine in the root namespace
resource "vault_mount" "kvv2" {
path = "my-kvv2"
type = "kv"
options = { version = "2" }
}
# Create a kv v2 secrets with the data_json_wo argument
resource "vault_kv_secret_v2" "db_root" {
mount = vault_mount.kvv2.path
name = "pgx-root"
data_json_wo = jsonencode(
{
password = "root-user-password"
}
)
data_json_wo_version = 1
}
# create an ephemeral vault_kv_secret_v2 resource
ephemeral "vault_kv_secret_v2" "db_secret" {
mount = vault_mount.kvv2.path
mount_id = vault_mount.kvv2.id
name = vault_kv_secret_v2.db_root.name
}
# mount a database secrets engine at the path "postgres"
resource "vault_mount" "db" {
path = "postgres"
type = "database"
}
# create a database role for the postgres database with a
# PostgreSQL Configuration Option password_wo
resource "vault_database_secret_backend_connection" "postgres" {
backend = vault_mount.db.path
name = "learn-postrgres"
allowed_roles = ["*"]
postgresql {
connection_url = "postgresql://{{username}}:{{password}}@localhost:5432/postgres?sslmode=disable"
password_authentication = ""
username = "postgres"
password_wo = tostring(ephemeral.vault_kv_secret_v2.db_secret.data.password)
password_wo_version = 1
}
}
To create a education/training
namespace, use the namespace
parameter to
point the vault_namespace.education.path
parent namespace.
resource "vault_namespace" "training" {
namespace = vault_namespace.education.path
path = "training"
}
Recomendation
It is strongly recommended to specify the target server specific
information using environment variables (e.g. VAULT_ADDR
, VAULT_TOKEN
); that is what you are going to do in this tutorial.
Note
If you are not familiar with Vault Enterprise namespace, refer to the Secure Multi-Tenancy with Namespaces tutorial.
In this example, we use the an ephemeral resource to set the root credential password for the PostgreSQL database.
Ephemeral resources create a temporary resource that is not persisted in Terraform state file or plan files. The user can access the data in their configurations but the information is not stored. Each ephemeral
block describes a resource, such as a temporary password.
Vault provider version 5.0.0 and later supports the ephemeral
block. The vault_kv_secret_v2
resource creates a secret in the kv-v2
secrets engine. The data_json_wo
argument specifies the secret data should be write only. It will not be written to the state file or displayed in the plan. The postgressql.password_wo
argument in the vault_database_secret_backend_connection
resource will use the ephemeral resource to set the password for the PostgreSQL database.
# create an ephemeral vault_kv_secret_v2 resource
ephemeral "vault_kv_secret_v2" "db_secret" {
mount = vault_mount.kvv2.path
mount_id = vault_mount.kvv2.id
name = vault_kv_secret_v2.db_root.name
}
Review policies.tf
Open the policies.tf
file and examine the
vault_policy
resources. It uses the provider
parameter to specify the target namespace to
create the policies.
policies.tf
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
#---------------------
# Create policies
#---------------------
# Create fpe-client policy in the root namespace
resource "vault_policy" "fpe_client_policy" {
name = "fpe-client"
policy = file("policies/fpe-client-policy.hcl")
}
# Create admin policy in the root namespace
resource "vault_policy" "admin_policy" {
name = "admins"
policy = file("policies/admin-policy.hcl")
}
# Create admin policy in the finance namespace
resource "vault_policy" "admin_policy_finance" {
namespace = vault_namespace.finance.path
name = "admins"
policy = file("policies/admin-policy.hcl")
}
# Create admin policy in the engineering namespace
resource "vault_policy" "admin_policy_engineering" {
namespace = vault_namespace.engineering.path
name = "admins"
policy = file("policies/admin-policy.hcl")
}
# Create admin policy in the education namespace
resource "vault_policy" "admin_policy_education" {
namespace = vault_namespace.education.path
name = "admins"
policy = file("policies/admin-policy.hcl")
}
# Create admin policy in the 'education/training' namespace
resource "vault_policy" "admin_policy_training" {
namespace = vault_namespace.training.path_fq
name = "admins"
policy = file("policies/admin-policy.hcl")
}
# Create admin policy in the 'education/training/vault_cloud' namespace
resource "vault_policy" "admin_policy_vault_cloud" {
namespace = vault_namespace.vault_cloud.path_fq
name = "admins"
policy = file("policies/admin-policy.hcl")
}
# Create admin policy in the 'education/training/boundary' namespace
resource "vault_policy" "admin_policy_boundary" {
namespace = vault_namespace.boundary.path_fq
name = "admins"
policy = file("policies/admin-policy.hcl")
}
Review auth.tf
Open the auth.tf
file.
- Line 4 through 6 enables
userpass
auth method. - Line 9 through 20 creates a user, "student" with
admins
andfpe-client
policies attached. The password is "changeme". - Line 25 through 29 enables
approle
auth method. - Line 32 through 38 creates a
test-role
role.
auth.tf
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
#--------------------------------
# Enable userpass auth method
#--------------------------------
resource "vault_auth_backend" "userpass" {
type = "userpass"
}
# Create a user named, "student"
resource "vault_generic_endpoint" "student" {
depends_on = [vault_auth_backend.userpass]
path = "auth/userpass/users/student"
ignore_absent_fields = true
data_json = <<EOT
{
"policies": ["fpe-client", "admins"],
"password": "changeme"
}
EOT
}
#--------------------------------------------------------------------
# Enable approle auth method in the 'education/training' namespace
#--------------------------------------------------------------------
resource "vault_auth_backend" "approle" {
depends_on = [vault_namespace.training]
namespace = vault_namespace.training.path_fq
type = "approle"
}
# Create a role named, "test-role"
resource "vault_approle_auth_backend_role" "test-role" {
depends_on = [vault_auth_backend.approle]
backend = vault_auth_backend.approle.path
namespace = vault_namespace.training.path_fq
role_name = "test-role"
token_policies = ["default", "admins"]
}
Review secrets.tf
Open the secrets.tf
file.
- Line 5 through 10 enables kv-v2 secretes engine in the
finance
namespace. - Line 19 through 53 defines a new alphabet, template, transformation, and a role.
- Line 59 through 69 tests the FPE transformation configured.
secrets.tf
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
#----------------------------------------------------------
# Enable secrets engines
#----------------------------------------------------------
# Enable kv-v2 secrets engine in the finance namespace
resource "vault_mount" "kv-v2" {
# depends_on = [vault_namespace.finance]
# provider = vault.finance
namespace = vault_namespace.finance.path
path = "kv-v2"
type = "kv-v2"
}
# Transform secrets engine at root
resource "vault_mount" "mount_transform" {
path = "transform"
type = "transform"
}
# Create an alphabet
resource "vault_transform_alphabet" "numerics" {
path = vault_mount.mount_transform.path
name = "numerics"
alphabet = "0123456789"
}
# Create a transformation template
resource "vault_transform_template" "ccn" {
path = vault_mount.mount_transform.path
name = "ccn"
type = "regex"
pattern = "(\\d{4})-(\\d{4})-(\\d{4})-(\\d{4})"
alphabet = vault_transform_alphabet.numerics.name
}
# Create a transformation named ccn-fpe
resource "vault_transform_transformation" "ccn-fpe" {
path = vault_mount.mount_transform.path
name = "ccn-fpe"
type = "fpe"
template = vault_transform_template.ccn.name
tweak_source = "internal"
# payments here listed by name and not reference to avoid circular dependency.
# Vault does not require dependencies like these to exist prior to creating
# other things that reference them, but they may simply not work until they do
# exist.
allowed_roles = ["payments"]
}
# Create a role named 'payments'
resource "vault_transform_role" "payments" {
path = vault_mount.mount_transform.path
name = "payments"
transformations = [vault_transform_transformation.ccn-fpe.name]
}
#-------------------------------------------------------------------
# Test the transformation
#-------------------------------------------------------------------
data "vault_transform_encode" "encoded" {
path = vault_transform_role.payments.path
role_name = "payments"
value = "1111-2222-3333-4444"
depends_on = [vault_transform_role.payments]
}
output "encoded" {
value = data.vault_transform_encode.encoded.encoded_value
}
Note
The details about the transformation, template, alphabet, and role are out of scope for this tutorial. If you are not familiar with Transform secrets engine, read the Transform sensitive data with Vault tutorial.
Lab setup
Open a terminal and export an environment variable with a valid Vault Enterprise license.
$ export VAULT_LICENSE=02MV4UU43BK5....
Open a terminal and start a Vault dev server with the literal string
root
as the root token value, and enable TLS.$ vault server -dev -dev-root-token-id root -dev-tls
The dev server listens on the loopback interface at 127.0.0.1 on TCP port 8200 with TLS enabled. At runtime, the dev server also automatically unseals, and prints the unseal key and initial root token values to the standard output.
Root tokens
The dev mode server starts with an initial root token value set.
Root token use should be extremely guarded in production environments because it provides full access to the Vault server.
You can supply the root token value to start Vault in dev mode for convenience and to keep the steps here focused on the learning goals of this tutorial.
In a new terminal, export the
VAULT_ADDR
andVAULT_CACERT
environment variables using the commands suggested in your Vault dev server output.Copy each command (without the
$
) from the server output, and paste it into the new terminal session.Example:
export VAULT_ADDR='https://127.0.0.1:8200'
Example:
$ export VAULT_CACERT='/var/folders/qr/zgztx0sj6n1dxy86sl36ntnw0000gn/T/vault-tls3037226588/vault-ca.pem'
Remember to use your dev server's values, not the examples shown here.
Export an environment variable for the
vault
CLI to authenticate with the Vault server.$ export VAULT_TOKEN=root
The Vault server is ready.
Run Terraform to configure Vault
Initialize Terraform to pull Vault provider plugin.
$ terraform init
This downloads the Vault plugin. When it completes, it displays a message,
Terraform has been successfully initialized!
Check the terraform plan for sernsitive information in the
vault_kv_secrets_v2
block$ terraform plan
Example output:
ephemeral.vault_kv_secret_v2.db_secret: Configuration unknown, deferring... Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create <= read (data resources) Terraform will perform the following actions: ... # vault_kv_secret_v2.db_root will be created + resource "vault_kv_secret_v2" "db_root" { + data = (sensitive value) + data_json_wo = (write-only attribute) + data_json_wo_version = 1 + delete_all_versions = false + disable_read = false + id = (known after apply) + metadata = (known after apply) + mount = "my-kvv2" + name = "pgx-root" + path = (known after apply) + custom_metadata (known after apply) }
Execute the
apply
command to configure Vault.$ terraform apply
Terraform displays the actions it will perform.
When prompted, enter
yes
to accept the plan and proceed with Vault configuration.Plan: 28 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes
Once completed, the output similar to the following displays.
Apply complete! Resources: 28 added, 0 changed, 0 destroyed. Outputs: encoded = "4079-1004-8601-9194"
Verify the configuration
List the existing namespaces.
$ vault namespace list Keys ---- education/ engineering/ finance/
List the nested namespaces.
$ vault namespace list -namespace=education Keys ---- training/
List namespaces under
training
.$ vault namespace list -namespace=education/training Keys ---- boundary/ vault_cloud/
Verify the creation of the policies.
$ vault policy list admins default fpe-client root
Verify that creation of the
admins
policy under thefinance
namespace.$ vault policy list -namespace=finance admins default
Similarly, verify the creation of
admins
policy under theengineering
namespace.$ vault policy list -namespace=engineering admins default
Verify creation of the
admins
policy under theeducation
namespace.$ vault policy list -namespace=education admins default
Verify the creation of the
admins
policy under theeducation/training
namespace.$ vault policy list -namespace=education/training admins default
Verify creation of the
admins
policy under theeducation/training/vault_cloud
namespace.$ vault policy list -namespace=education/training/vault_cloud admins default
Verify the creation of the
admins
policy under theeducation/training/boundary
namespace.$ vault policy list -namespace=education/training/boundary admins default
Verify the enablement of the kv-v2 secrets engine in the
finance
namespace.$ vault secrets list -namespace=finance Path Type Accessor Description ---- ---- -------- ----------- cubbyhole/ ns_cubbyhole ns_cubbyhole_f615f334 per-token private secret storage identity/ ns_identity ns_identity_1ae45907 identity store kv-v2/ kv kv_abd3188e n/a sys/ ns_system ns_system_e5b43cff system endpoints used for control, policy and debugging
Verify the transformation secrets engine configuration for credit card numbers.
List existing transformations.
$ vault list transform/transformation Keys ---- ccn-fpe
Read the
ccn-fpe
transformation details.$ vault read transform/transformation/ccn-fpe Key Value --- ----- allowed_roles [payments] templates [ccn] tweak_source internal type fpe
List existing transformation templates.
$ vault list transform/template Keys ---- builtin/creditcardnumber builtin/socialsecuritynumber ccn
Read the
ccn
transformation template definition.$ vault read transform/template/ccn Key Value --- ----- alphabet numerics decode_formats map[] encode_format n/a pattern (\d{4})-(\d{4})-(\d{4})-(\d{4}) type regex
Now, verify that you can log in with
userpass
auth method using the username, "student" and password, "changeme".$ vault login -method=userpass username=student Password (will be hidden):
Enter the password
changeme
when prompted, and pressreturn
.Password (will be hidden): Key Value --- ----- token s.5MadPX3UQgBRuukSHjf9yPJd token_accessor AKvdeYEJspzN4fdOq0OcSSps token_duration 768h token_renewable true token_policies ["admins" "default" "fpe-client"] identity_policies [] policies ["admins" "default" "fpe-client"] token_meta_username student
The generated token has
admins
andfpe-client
policies attached. Now, take a look at thefpe-client
policy definition.$ cat policies/fpe-client-policy.hcl
The
fpe-client
policy permits update operation against thetransform/encode/*
andtransform/decode/*
paths.# To request data encoding using any of the roles # Specify the role name in the path to narrow down the scope path "transform/encode/*" { capabilities = [ "update" ] } # To request data decoding using any of the roles # Specify the role name in the path to narrow down the scope path "transform/decode/*" { capabilities = [ "update" ] }
The student user should be able to perform format-preserving encryption (FPE) transformation.
$ vault write transform/encode/payments value=1111-2222-3333-4444 Key Value --- ----- encoded_value 6754-4542-1216-8423
Verify that you can decode the encoded value.
$ vault write transform/decode/payments value="6754-4542-1216-8423" Key Value --- ----- decoded_value 1111-2222-3333-4444
Successfully returns the original credit card number.
Check that you have enabled the
approle
auth method in theeducation/training
namespace.$ vault auth list -namespace=education/training Path Type Accessor Description ---- ---- -------- ----------- approle/ approle auth_approle_077406b6 n/a token/ ns_token auth_ns_token_99292818 token based credentials
- Verify that
test-role
exists.
$ vault list -namespace=education/training auth/approle/role Keys ---- test-role
- Verify that
Verify ephemeral resources
The ephemeral resources will not store anyting in the Terraform state file.
Verify that the password was not saved in the Terraform state file.
$ cat terraform.tfstate | grep root-user-password
The
data_json_wo
argument in thevault_kv_secret_v2
resource does not store the password in the Terraform state file. The output should be empty.$ cat terraform.tfstate | grep data_json_wo "data_json_wo": null, "data_json_wo_version": 1,
Clean up
When you finish exploring the tutorial, you can undo the configuration made by Terraform.
Make sure to set the
VAULT_TOKEN
andVAULT_ADDR
environment variables.$ echo $VAULT_ADDR; echo $VAULT_TOKEN
Destroy the Vault resources created by Terraform.
$ terraform destroy -auto-approve ...snip... Destroy complete! Resources: 24 destroyed.
Remove the Terraform state files.
$ rm *.tfstate.*
Unset the
VAULT_TOKEN
andVAULT_ADDR
environment variables.$ unset VAULT_TOKEN VAULT_ADDR
Stop the Vault dev mode server.
$ pkill vault
Note
To learn more about Terraform, visit Learn Terraform.
Next steps
Treat your Terraform files like any other code and manage them through a version control system such as GitHub. You may integrate it with your favorite CI/CD tool (Jenkins, Travis CI, Circle CI, etc.), always review and test the configuration.

See the Retrieve CI/CD secrets from Vault for more information on how to integrate Vault with your CI/CD pipeline.
Summary
In this guide you learned a technique for creating Vault policies and configurations using the Terraform Vault provider. For more information, see the help and reference section.
Help and reference
- Terraform Vault provider documentation page
- Terraform Vault provider GitHub repository
- Learn Terraform
- Multi-tenancy with Namespaces
Tip
Terraform users can leverage the Vault's dynamic secrets engine to generate short-live cloud credentials when provisioning cloud resources. Inject secrets into Terraform using the Vault provider tutorial demonstrates the use of AWS secrets engine to manage AWS IAM credentials used by Terraform.