Vault
Codify Vault Enterprise management with Terraform
Personas
The scenario described in this tutorial introduces the following personas:
- adminis the organization-level administrator
- studentis 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 Vault Community, see the Learn to use the Vault Terraform provider 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 educationdedicated to the training team | 
| namespace | vault_cloud | A child-namespace under education/trainingdedicated to the vault_cloud team | 
| namespace | engineering | A child-namespace under education/trainingdedicated 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 adminsandfpe-clientpolicies | 
| auth method | approle | Enable approle auth method in the education/trainingand create atest-rolerole | 
| secrets engine | kv-v2 | Enable kv-v2 secrets engine in the financenamespace | 
| 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. The required_providers block declares the provider requirements
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 userpassauth method.
- Line 9 through 20 creates a user, "student" with adminsandfpe-clientpolicies attached. The password is "changeme".
- Line 25 through 29 enables approleauth method.
- Line 32 through 38 creates a test-rolerole.
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 financenamespace.
- 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 - rootas 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_ADDRand- VAULT_CACERTenvironment 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 - vaultCLI 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 sensitive information in the - vault_kv_secrets_v2block- $ 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 - applycommand to configure Vault.- $ terraform apply- Terraform displays the actions it will perform. - When prompted, enter - yesto 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 - adminspolicy under the- financenamespace.- $ vault policy list -namespace=finance admins default- Similarly, verify the creation of - adminspolicy under the- engineeringnamespace.- $ vault policy list -namespace=engineering admins default- Verify creation of the - adminspolicy under the- educationnamespace.- $ vault policy list -namespace=education admins default- Verify the creation of the - adminspolicy under the- education/trainingnamespace.- $ vault policy list -namespace=education/training admins default- Verify creation of the - adminspolicy under the- education/training/vault_cloudnamespace.- $ vault policy list -namespace=education/training/vault_cloud admins default- Verify the creation of the - adminspolicy under the- education/training/boundarynamespace.- $ vault policy list -namespace=education/training/boundary admins default
- Verify the enablement of the kv-v2 secrets engine in the - financenamespace.- $ 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-fpetransformation 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 - ccntransformation 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 - userpassauth method using the username, "student" and password, "changeme".- $ vault login -method=userpass username=student Password (will be hidden):- Enter the password - changemewhen prompted, and press- return.- 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 - adminsand- fpe-clientpolicies attached. Now, take a look at the- fpe-clientpolicy definition.- $ cat policies/fpe-client-policy.hcl- The - fpe-clientpolicy permits update operation against the- transform/encode/*and- transform/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 - approleauth method in the- education/trainingnamespace.- $ 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-roleexists.
 - $ 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_woargument in the- vault_kv_secret_v2resource 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_TOKENand- VAULT_ADDRenvironment 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_TOKENand- VAULT_ADDRenvironment 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.











