Vault
Manage access to Vault with joint controller authorization
Enterprise only
Control groups require a Vault Enterprise Plus license or HCP Vault Dedicated plus tier cluster.
Control groups allow you to require additional authorization factors before processing requests. These additional authorizations help you increase the governance, accountability, and security of your secrets. When you specify a control group for a request, the requesting client receives the wrapping token in return. When all authorizations are satisfied, the client can use the wrapping token to unwrap the requested secrets.
Challenge
To operate in EU, a company must abide by the General Data Protection Regulation (GDPR) as of May 2018. The regulation enforces two or more controllers jointly determine the purposes and means of processing (Chapter 4: Controller and Processor).
Consider the following scenarios:
Anytime an authorized user requests to read data at "
EU_GDPR_data/data/orders/*
", at least two people from the security group must approve to ensure that the user has a valid business reason for requesting the data.Anytime you update a database configuration, it requires that one person from the DBA group and one person from the security group must approve it.
Solution
Use control groups in your policies to implement dual controller authorization requirements.
Prerequisites
To perform the tasks described in this tutorial, you need to have the following:
An HCP Vault Dedicated cluster or a Vault Enterprise environment and license.
jq installed to process the JSON output for readability.
This tutorial assumes that you have some hands-on experience with ACL policies as well as identities. If you are not familiar, go through the Vault get started tutorial series.
Policy requirements
Since this tutorial demonstrates the creation of policies, log in with a highly
privileged token such as root
. Otherwise, review the required permissions to perform the
steps in this tutorial.
# Create and manage ACL policies
path "sys/policies/acl/*"
{
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
# To enable secrets engines
path "sys/mounts/*" {
capabilities = [ "create", "read", "update", "delete" ]
}
# Setting up test data
path "EU_GDPR_data/*"
{
capabilities = ["create", "read", "update", "delete", "list"]
}
# Manage userpass auth method
path "auth/userpass/*"
{
capabilities = ["create", "read", "update", "delete", "list"]
}
# List, create, update, and delete auth methods
path "sys/auth/*"
{
capabilities = ["create", "read", "update", "delete"]
}
# Create and manage entities and groups
path "identity/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
}
Scenario introduction
The scenario in this tutorial is that a user, Bob Smith
has
read-only permission on the "EU_GDPR_data/data/orders/*
" path; however,
someone in the acct_manager
group must approve it before he can actually
read the data.
As a member of the acct_manager
group, Ellen Wright
can authorize
Bob's request.

Personas:
- admin with privileged permissions to create policies and identities
- processor with limited permission to access secrets
- controller with permission to approve secret access
Scenario workflow
You are going to perform the following:
- Lab setup
- Implement a control group
- Deploy the policies
- Setup entities and a group
- Test the control group
If you want to implement control groups in Sentinel, read the ACL Policies vs. Sentinel Policies section.
Lab setup
Note
You must use a HCP Vault Dedicated plus-tier cluster to complete this tutorial. Visit the Create a Vault Cluster on HCP tutorial for the steps to create a cluster.
Launch the HCP Portal and login.
Click Vault in the left navigation pane.
In the Vault clusters pane, click vault-cluster.
Under Cluster URLs, click Public Cluster URL.
In a terminal, set the
VAULT_ADDR
environment variable to the copied address.$ export VAULT_ADDR=<Public_Cluster_URL>
Return to the Overview page and click Generate token.
Copy the Admin Token.
Return to the terminal and set the
VAULT_TOKEN
environment variable.$ export VAULT_TOKEN=<token>
Set the
VAULT_NAMESPACE
environment variable toadmin
.$ export VAULT_NAMESPACE=admin
The
admin
namespace is the top-level namespace automatically created by HCP Vault. All CLI operations default to use the namespace defined in this environment variable.Type
vault status
to verify your connectivity to the Vault cluster.$ vault status Key Value --- ----- Recovery Seal Type shamir Initialized true Sealed false Total Recovery Shares 1 Threshold 1 Version 1.9.2+ent Storage Type raft ...snipped...
The Vault Dedicated server is ready.
Implement a control group
(Persona: admin)
Create a policy file named
read-gdpr-order.hcl
.$ tee read-gdpr-order.hcl <<EOF path "EU_GDPR_data/data/orders/*" { capabilities = [ "read" ] control_group = { factor "authorizer" { identity { group_names = [ "acct_manager" ] approvals = 1 } } } } EOF
Examine the policy
The condition is that Bob can
read
the secrets atEU_GDPR_data/data/orders/*
if someone from theacct_manager
group approves.read-gdpr-order.hcl
path "EU_GDPR_data/data/orders/*" { capabilities = [ "read" ] control_group = { factor "authorizer" { identity { group_names = [ "acct_manager" ] approvals = 1 } } } }
For the purpose of this tutorial, set the number of
approvals
to1
. Any member of the identity group,acct_manager
can approve the read request. Although this example has only one factor (authorizer
), you can add more factor blocks to meet your needs.Create another policy file named,
acct_manager.hcl
. This is the policy needed for the member of controller (acct_manager
) to approve Bob's request.$ tee acct_manager.hcl <<EOF # To approve the request path "sys/control-group/authorize" { capabilities = ["create", "update"] } # To check control group request status path "sys/control-group/request" { capabilities = ["create", "update"] } EOF
The authorizer must have
create
andupdate
capabilities on thesys/control-group/authorize
endpoint so that they can approve the request.
Deploy the policies
(Persona: admin)
Deploy the read-gdpr-order
and acct_manager
policies that you wrote.
Create a new policy named
read-gdpr-order
.$ vault policy write read-gdpr-order read-gdpr-order.hcl Success! Uploaded policy: read-gdpr-order
Create a new policy named
acct_manager
.$ vault policy write acct_manager acct_manager.hcl Success! Uploaded policy: acct_manager
Setup entities and a group
(Persona: admin)
Now that you have policies created, create a user bob
, and an acct_manager
group with
ellen
as a group member.
Note
For the purpose of this tutorial, use the userpass
auth method to
create the users bob
and ellen
so you can test the scenario.
Enable the
userpass
auth method.$ vault auth enable userpass Success! Enabled userpass auth method at: userpass/
Create a new user,
bob
with password, "training".$ vault write auth/userpass/users/bob password="training" Success! Data written to: auth/userpass/users/bob
Create a new user,
ellen
with password, "training".$ vault write auth/userpass/users/ellen password="training" Success! Data written to: auth/userpass/users/ellen
Retrieve the userpass mount accessor and save it in a file named
accessor.txt
.$ vault auth list -format=json | jq -r '.["userpass/"].accessor' > accessor.txt
Create
Bob Smith
entity and save the identity ID in theentity_id_bob.txt
.$ vault write -format=json identity/entity name="Bob Smith" \ policies="read-gdpr-order" \ metadata=team="Processor" \ | jq -r ".data.id" > entity_id_bob.txt
Add an entity alias for the
Bob Smith
entity.$ vault write identity/entity-alias name="bob" \ canonical_id=$(cat entity_id_bob.txt) \ mount_accessor=$(cat accessor.txt)
Example output:
Key Value --- ----- canonical_id e57d0eff-ca1d-d4d8-b7b7-2b3f842b811d id 09bdc8af-9bb2-950d-ac63-cfb96fce4d77
Create
Ellen Wright
entity and save the identity ID in theentity_id_ellen.txt
.$ vault write -format=json identity/entity name="Ellen Wright" \ policies="default" \ metadata=team="Acct Controller" \ | jq -r ".data.id" > entity_id_ellen.txt
Add an entity alias for the
Ellen Wright
entity.$ vault write identity/entity-alias name="ellen" \ canonical_id=$(cat entity_id_ellen.txt) \ mount_accessor=$(cat accessor.txt)
Example output:
Key Value --- ----- canonical_id 78762f37-a651-229d-6db3-a25cbb71070f id 94499a59-5890-9aef-3810-37ffdc6b7511
Create
acct_manager
group and addEllen Wright
entity as a member.$ vault write identity/group name="acct_manager" \ policies="acct_manager" \ member_entity_ids=$(cat entity_id_ellen.txt)
Example output:
Key Value --- ----- id 898f0f44-e2c1-efcf-7a55-2c1c6c4bf1ae name acct_manager
Test the control group
(Persona: admin)
Validate the control groups work by request a secret as Bob and approving the request as Ellen.
Enable the key/value secrets engine at
EU_GDPR_data
.$ vault secrets enable -path=EU_GDPR_data -version=2 kv Success! Enabled the kv secrets engine at: EU_GDPR_data/
Write some mock data.
$ vault kv put EU_GDPR_data/orders/acct1 \ order_number="12345678" product_id="987654321"
Example output:
========= Secret Path ========= EU_GDPR_data/data/orders/acct1 ======= Metadata ======= Key Value --- ----- created_time 2022-05-18T19:11:20.105114Z custom_metadata <nil> deletion_time n/a destroyed false version 1
(Persona: processor)
Unset the
root
Vault token.$ unset VAULT_TOKEN
Log in as
bob
.$ vault login -method=userpass username="bob" Password (will be hidden):
Enter the password
training
when prompted, and pressreturn
.Request to read "
EU_GDPR_data/orders/acct1
".$ vault kv get EU_GDPR_data/orders/acct1
Example output:
Key Value --- ----- wrapping_token: hvs.AmhW6ULQ7Eoy6o5JMHr679Z0 wrapping_accessor: Vbs3d5FGbeox1mjYOgLWvXsP wrapping_token_ttl: 24h wrapping_token_creation_time: 2021-07-21 19:42:35 -0700 PDT wrapping_token_creation_path: EU_GDPR_data/data/orders/acct1
The response includes
wrapping_token
andwrapping_accessor
. Export their values as environment variables for use in later steps.Note
Be sure to replace the example
wrapping_token
andwrapping_accessor
values with your actual values.Export the wrapping token value as
WRAPPING_TOKEN
.$ export WRAPPING_TOKEN=<wrapping_token>
Export the wrapping token accessor as
WRAPPING_ACCESSOR
.$ export WRAPPING_ACCESSOR=<wrapping_accessor>
Example:
$ export WRAPPING_TOKEN=hvs.AmhW6ULQ7Eoy6o5JMHr679Z0 $ export WRAPPING_ACCESSOR=Vbs3d5FGbeox1mjYOgLWvXsP
(Persona: controller)
A user who is a member of the acct_manager
group can check
and authorize Bob's request using the request
and authorize
commands.
Log in as
ellen
who is a member ofacct_manager
group.$ vault login -method=userpass username="ellen" Password (will be hidden):
Enter the password
training
when prompted, and pressreturn
.Check the current status.
$ vault write sys/control-group/request accessor=$WRAPPING_ACCESSOR Key Value --- ----- approved false authorizations <nil> request_entity map[name:Bob Smith id:38700386-723d-3d65-43b7-4fb44d7e6c30] request_path EU_GDPR_data/orders/acct1
The
approved
status isfalse
since Ellen has not approved the request.Approve the request.
$ vault write sys/control-group/authorize accessor=$WRAPPING_ACCESSOR Key Value --- ----- approved true
Now, the
approved
status istrue
.
(Persona: processor)
Since the control group requires one approval from a member of acct_manager
group, you have met the required condition. Log back in as bob
and unwrap the
secret.
Log back in as bob using the userpass auth method.
$ vault login -method=userpass username="bob" Password (will be hidden):
Enter the password
training
when prompted, and pressreturn
.Unwrap the secrets by passing the
$WRAPPING_TOKEN
environment variable.$ vault unwrap $WRAPPING_TOKEN Key Value --- ----- data map[order_number:12345678 product_id:987654321] metadata map[created_time:2021-03-24T16:25:20.405776245Z deletion_time: destroyed:false version:1]
Define a control group for operations
Assume that Bob's token has read-paris
and change-paris
policies attached.
The read-paris
allows Bob to perform read and list operations against the
paris-kv/*
path.
read-paris.hcl
path "paris-kv/*" {
capabilities = [ "read", "list" ]
}
The change-paris
policy requires 1 approval from eng-managers
group if Bob
tries to perform create, update or delete operation against the paris-kv/*
path.
change-paris.hcl
path "paris-kv/*" {
capabilities = [ "create", "update", "delete" ]
control_group = {
factor "managers" {
identity {
group_names = [ "eng-managers" ]
approvals = 1
}
}
}
}
The intention was that Bob can read or list secrets at paris-kv/*
without
authorization. However, with both of those policies applied, even the read
and list operations will trigger the control groups. The read and list
capabilities from the read-paris
policy get aggregated into the change-paris
capabilities list.
Controlled capabilities
To solve this, use controlled_capabilities
in the policy to narrow the scope
of control group to the operation level.
restrict-paris.hcl
path "paris-kv/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
control_group = {
factor "managers" {
controlled_capabilities = [ "create", "update", "delete" ]
identity {
group_names = [ "acct_manager" ]
approvals = 1
}
}
}
}
Test controlled capabilities
Log in with the admin persona's token.
Note
For Vault Enterprise, use
root
as the token value. For HCP Vault Dedicated, use the admin token from the HCP Portal.$ vault login Token (will be hidden):
Enter the actual token value when prompted, and press
return
.Create a
restrict-paris
policy.$ tee restrict-paris.hcl <<EOF path "paris-kv/*" { capabilities = [ "create", "read", "update", "delete", "list" ] control_group = { factor "managers" { controlled_capabilities = [ "create", "update", "delete" ] identity { group_names = [ "acct_manager" ] approvals = 1 } } } } EOF
Deploy the
restrict-paris
policy.$ vault policy write restrict-paris restrict-paris.hcl Success! Uploaded policy: restrict-paris
Add the policy to Bob's entity.
$ vault write identity/entity/id/$(cat entity_id_bob.txt) \ policies="read-gdpr-order,restrict-paris"
Example output:
Success! Data written to: identity/entity/id/84f39aab-8ae2-a647-6a9d-hiJKlmnop
Enable
kv-v2
secrets engine atparis-kv
.$ vault secrets enable -path="paris-kv" kv-v2 Success! Enabled the kv-v2 secrets engine at: paris-kv/
Create some mock data at
paris-kv
.$ vault kv put paris-kv/product name="Boundary" version="0.4.0" ==== Secret Path ==== paris-kv/data/product ======= Metadata ======= Key Value --- ----- created_time 2022-05-18T19:18:16.553795Z custom_metadata <nil> deletion_time n/a destroyed false version 1
Log in as
bob
.$ vault login -method=userpass username="bob" Password (will be hidden):
Enter the password
training
when prompted, and pressreturn
.The returned token should have
restrict-paris
policy attached.Example output:
Key Value --- ----- token hvs.vAUdsbhpSZv0CqsC5qAqdaQ3 token_accessor VHuhVHCxzZlWh4qbXTHaPGak token_duration 768h token_renewable true token_policies ["default"] identity_policies ["read-gdpr-order" "restrict-paris"] policies ["default" "read-gdpr-order" "restrict-paris"] token_meta_username bob
Read the secrets at
paris-kv/product
.$ vault kv get paris-kv/product ==== Secret Path ==== paris-kv/data/product ======= Metadata ======= Key Value --- ----- created_time 2022-05-18T19:18:16.553795Z custom_metadata <nil> deletion_time n/a destroyed false version 1 ===== Data ===== Key Value --- ----- name Boundary version 0.4.0
You should be able to read the secrets without triggering the control group.
Try to delete the secrets at
paris-kv/product
.$ vault kv delete paris-kv/product
This time, Vault returns
wrapping_token
andwrapping_accessor
.Example output:
Key Value --- ----- wrapping_token: hvs.6myj2lXu7xg39CjrUFiLrwSV wrapping_accessor: X9AISHC5GSMGyDNpoYG3tw8s wrapping_token_ttl: 24h wrapping_token_creation_time: 2021-07-21 22:12:16 -0700 PDT wrapping_token_creation_path: paris-kv/data/releases
Optional: If you want to complete the rest of the workflow, repeat the steps you performed earlier in the Test the control group section.
- Login as
ellen
. - Authorize Bob's request using the wrapping accessor value.
- Login as
bob
. - Unwrap the secrets using the wrapping token.
ACL policy vs. Sentinel policy
Although this tutorial shows writing
read-gdpr-order.hcl
as an ACL policy, you
can implement control groups in either ACL or Sentinel policies.
Using Sentinel, the same policy may look something like.
read-gdpr-order.sentinel
import "controlgroup"
control_group = func() {
numAuthzs = 0
for controlgroup.authorizations as authz {
if "acct_manager" in authz.groups.by_name {
numAuthzs = numAuthzs + 1
}
}
if numAuthzs >= 1 {
return true
}
return false
}
main = rule {
control_group()
}
Deploy this policy as an Endpoint Governing Policy attached to
"EU_GDPR_data/data/orders/*
" path.
Tip
Refer to the Sentinel Properties documentation for the list of available properties associated with control groups. If you are new to Sentinel, go through the Sentinel Policies tutorial.