»Control Groups
Enterprise Only: Control Groups is a part of Vault Enterprise Premium.
Control Groups add additional authorization factors to be required before
processing requests to increase the governance, accountability, and security of
your secrets. When a control group is required for a request, the requesting
client receives the wrapping token in
return. Only when all authorizations are satisfied, the wrapping token can be
used to unwrap the requested secrets.
»Reference Material
»Estimated Time to Complete
10 minutes
»Personas
The end-to-end scenario described in this guide involves three personas:
-
admin
with privileged permissions to create policies and identities
-
processor with permission to approve secret access
-
controller with limited permission to access secrets
»Challenge
In order 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/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 a database configuration is updated, it requires that one person from
the DBA and one person from Security group must approve it.
»Solution
Use Control Groups in your policies to implement dual controller
authorization required.
»Prerequisites
To perform the tasks described in this guide, you need to have a Vault
Enterprise environment.
This guide assumes that you have some hands-on experience with ACL
policies as well as
Identities. If you are not familiar,
go through the following guides first:
»Policy requirements
Since this guide demonstrates the creation of policies, log in with a highly
privileged token such as root
.
Otherwise, required permissions to perform
the steps in this guide are:
path "sys/policy/*"
{
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
path "sys/policies/acl/*"
{
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
path "sys/mounts/*" {
capabilities = [ "create", "read", "update", "delete" ]
}
path "EU_GDPR_data/*"
{
capabilities = ["create", "read", "update", "delete", "list"]
}
path "auth/userpass/*"
{
capabilities = ["create", "read", "update", "delete", "list"]
}
path "sys/auth/*"
{
capabilities = ["create", "read", "update", "delete"]
}
path "identity/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
}
path "sys/policy/*"{ capabilities = ["create", "read", "update", "delete", "list", "sudo"]}
path "sys/policies/acl/*"{ capabilities = ["create", "read", "update", "delete", "list", "sudo"]}
path "sys/mounts/*" { capabilities = [ "create", "read", "update", "delete" ]}
path "EU_GDPR_data/*"{ capabilities = ["create", "read", "update", "delete", "list"]}
path "auth/userpass/*"{ capabilities = ["create", "read", "update", "delete", "list"]}
path "sys/auth/*"{ capabilities = ["create", "read", "update", "delete"]}
path "identity/*" { capabilities = [ "create", "read", "update", "delete", "list" ]}
»Steps
The scenario in this guide is that a user, Bob Smith
has
read-only permission on the "EU_GDPR_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.

You are going to perform the following:
- Implement a control group
- Deploy the policies
- Setup entities and a group
- Verification
- ACL Policies vs. Sentinel Policies
Step 1, 2 and 3 are the tasks need to be performed by administrators or
operators who have the privileges to create policies and configure entities and
groups.
»Step 1: Implement a control group
(Persona: admin)
-
Author a policy named, read-gdpr-order.hcl
.
Bob needs "read
" permit on "EU_GDPR_data/orders/*
":
path "EU_GDPR_data/orders/*" {
capabilities = [ "read" ]
}
path "EU_GDPR_data/orders/*" { capabilities = [ "read" ]}
Now, add control group to this policy:
path "EU_GDPR_data/orders/*" {
capabilities = [ "read" ]
control_group = {
factor "authorizer" {
identity {
group_names = [ "acct_manager" ]
approvals = 1
}
}
}
}
path "EU_GDPR_data/orders/*" { capabilities = [ "read" ]
control_group = { factor "authorizer" { identity { group_names = [ "acct_manager" ] approvals = 1 } } }}
For the purpose of this guide, the number of approvals
is set to
1
to keep it simple and easy to test. Any member of the identity
group, acct_manager
can approve the read request. Although this
example has only one factor (authorizer
), you can add as many factor
blocks as you need.
-
Now, write another policy for the acct_manager
group named
acct_manager.hcl
.
path "sys/control-group/authorize" {
capabilities = ["create", "update"]
}
path "sys/control-group/request" {
capabilities = ["create", "update"]
}
path "sys/control-group/authorize" { capabilities = ["create", "update"]}
path "sys/control-group/request" { capabilities = ["create", "update"]}
NOTE: The important thing here is that the authorizer (acct_manager
)
must have create
and update
permission on the
sys/control-group/authorize
endpoint so that they can approve the request.
-
Enable key/value secrets engine at EU_GDPR_data
and write some mock data:
$ vault secrets enable -path=EU_GDPR_data -version=1 kv
$ vault kv put EU_GDPR_data/orders/acct1 order_number="12345678" product_id="987654321"
$ vault secrets enable -path=EU_GDPR_data -version=1 kv
$ vault kv put EU_GDPR_data/orders/acct1 order_number="12345678" product_id="987654321"
»Step 2: Deploy the policies
(Persona: admin)
Deploy the read-gdpr-order
and acct_manager
policies that you wrote.
»CLI command
$ vault policy write read-gdpr-order read-gdpr-order.hcl
$ vault policy write acct_manager acct_manager.hcl
$ vault policy write read-gdpr-order read-gdpr-order.hcl
$ vault policy write acct_manager acct_manager.hcl
»API call using cURL
$ tee payload-1.json <<EOF
{
"policy": "path \"EU_GDPR_data/orders/*\" {capabilities = [ \"read\" ]control_group = {factor \"authorizer\" ..."
}
EOF
$ curl --header "X-Vault-Token: ..." \
--request PUT \
--data @payload-1.json \
http://127.0.0.1:8200/v1/sys/policies/acl/read-gdpr-order
$ tee payload-2.json <<EOF
{
"policy": "path \"sys/control-group/authorize\" {capabilities = [\"create\", \"update\"]} ..."
}
EOF
$ curl --header "X-Vault-Token: ..." \
--request PUT \
--data @payload-2.json \
http://127.0.0.1:8200/v1/sys/policies/acl/acct_manager
$ tee payload-1.json <<EOF{ "policy": "path \"EU_GDPR_data/orders/*\" {capabilities = [ \"read\" ]control_group = {factor \"authorizer\" ..."}EOF
$ curl --header "X-Vault-Token: ..." \ --request PUT \ --data @payload-1.json \ http://127.0.0.1:8200/v1/sys/policies/acl/read-gdpr-order
$ tee payload-2.json <<EOF{ "policy": "path \"sys/control-group/authorize\" {capabilities = [\"create\", \"update\"]} ..."}EOF
$ curl --header "X-Vault-Token: ..." \ --request PUT \ --data @payload-2.json \ http://127.0.0.1:8200/v1/sys/policies/acl/acct_manager
»Web UI
Open a web browser and launch the Vault UI (e.g. http://127.0.0.1:8200/ui) and
then login.
Click the Policies tab, and then select Create ACL policy.
-
Toggle Upload file, and click Choose a file to select your read-gdpr-order.hcl
file you authored at Step 1.

This loads the policy and sets the Name to be read-gdpr-order
.
Click Create Policy to complete.
Repeat the steps to create a policy for acct_manager
.
»Step 3: Setup entities and a group
(Persona: admin)
This step only demonstrates CLI commands and Web UI to create
entities and groups. Refer to the Identity - Entities and
Groups guide if you need the full details.
Now you have policies, let's create a user, bob
and an acct_manager
group with ellen
as a group member.
NOTE: For the purpose of this guide, use userpass
auth method to create
user bob
and ellen
so that the scenario can be easily tested.
»CLI command
The following command uses jq
tool
to parse JSON output.
$ vault auth enable userpass
$ vault write auth/userpass/users/bob password="training"
$ vault write auth/userpass/users/ellen password="training"
$ vault auth list -format=json | jq -r '.["userpass/"].accessor' > accessor.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
$ vault write identity/entity-alias name="bob" \
canonical_id=$(cat entity_id_bob.txt) \
mount_accessor=$(cat accessor.txt)
$ vault write -format=json identity/entity name="Ellen Wright" policies="default" \
metadata=team="Acct Controller" \
| jq -r ".data.id" > entity_id_ellen.txt
$ vault write identity/entity-alias name="ellen" \
canonical_id=$(cat entity_id_ellen.txt) \
mount_accessor=$(cat accessor.txt)
$ vault write identity/group name="acct_manager" \
policies="acct_manager" \
member_entity_ids=$(cat entity_id_ellen.txt)
$ vault auth enable userpass
$ vault write auth/userpass/users/bob password="training"
$ vault write auth/userpass/users/ellen password="training"
$ vault auth list -format=json | jq -r '.["userpass/"].accessor' > accessor.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
$ vault write identity/entity-alias name="bob" \ canonical_id=$(cat entity_id_bob.txt) \ mount_accessor=$(cat accessor.txt)
$ vault write -format=json identity/entity name="Ellen Wright" policies="default" \ metadata=team="Acct Controller" \ | jq -r ".data.id" > entity_id_ellen.txt
$ vault write identity/entity-alias name="ellen" \ canonical_id=$(cat entity_id_ellen.txt) \ mount_accessor=$(cat accessor.txt)
$ vault write identity/group name="acct_manager" \ policies="acct_manager" \ member_entity_ids=$(cat entity_id_ellen.txt)
»Web UI
Click the Access tab, and select Enable new method.
Select Username & Password from the Type drop-down menu.
Click Enable Method.
-
Click the Vault CLI shell icon (>_
) to open a command shell. Enter the
following command to create a new user, bob
:
$ vault write auth/userpass/users/bob password="training"
$ vault write auth/userpass/users/bob password="training"

-
Enter the following command to create a new user, ellen
:
$ vault write auth/userpass/users/ellen password="training"
$ vault write auth/userpass/users/ellen password="training"
Click the icon (>_
) again to hide the shell.
From the Access tab, select Entities and then Create entity.
-
Populate the Name, Policies and Metadata fields as shown below.

Click Create.
Select Add alias. Enter bob
in the Name field and select
userpass/ (userpass)
from the Auth Backend drop-down list.
Return to the Entities tab and then Create entity.
-
Populate the Name, Policies and Metadata fields as shown below.

Click Create.
Select Add alias. Enter ellen
in the Name field and select
userpass/ (userpass)
from the Auth Backend drop-down list.
Click Create.
Select the Ellen Wright
entity and copy its ID displayed under the
Details tab.
Click Groups from the left navigation, and select Create group.
Enter acct_manager
in the Name, and again enter acct_manager
in the Policies fields.
Enter the Ellen Wright
entity ID in the Member Entity IDs field, and
then click Create.
»Step 4: Verification
(Persona: bob and ellen)
Now, let's see how the control group works.
»CLI Command
-
Log in as bob
.
$ vault login -method=userpass username="bob" password="training"
$ vault login -method=userpass username="bob" password="training"
-
Request to read "EU_GDPR_data/orders/acct1
":
$ vault kv get EU_GDPR_data/orders/acct1
Key Value
--- -----
wrapping_token: 1f1411bc-2f18-551a-5e58-0fe44432e9a5
wrapping_accessor: bbb4deef-e06d-9b2a-64a9-56f815c69ee7
wrapping_token_ttl: 24h
wrapping_token_creation_time: 2018-08-08 09:36:32 -0700 PDT
wrapping_token_creation_path: EU_GDPR_data/orders/acct1
$ vault kv get EU_GDPR_data/orders/acct1
Key Value--- -----wrapping_token: 1f1411bc-2f18-551a-5e58-0fe44432e9a5wrapping_accessor: bbb4deef-e06d-9b2a-64a9-56f815c69ee7wrapping_token_ttl: 24hwrapping_token_creation_time: 2018-08-08 09:36:32 -0700 PDTwrapping_token_creation_path: EU_GDPR_data/orders/acct1
The response includes wrapping_token
and wrapping_accessor
.
Copy this wrapping_accessor
value.
-
Now, a member of acct_manager
must approve this request. Log in as
ellen
who is a member of acct_manager
group.
$ vault login -method=userpass username="ellen" password="training"
$ vault login -method=userpass username="ellen" password="training"
-
As a user, ellen
, you can check and authorize bob's request using the
following commands.
$ vault write sys/control-group/request accessor=<wrapping_accessor>
$ vault write sys/control-group/authorize accessor=<wrapping_accessor>
$ vault write sys/control-group/request accessor=<wrapping_accessor>
$ vault write sys/control-group/authorize accessor=<wrapping_accessor>
Example:
$ vault write sys/control-group/request accessor=bbb4deef-e06d-9b2a-64a9-56f815c69ee7
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
$ vault write sys/control-group/authorize accessor=bbb4deef-e06d-9b2a-64a9-56f815c69ee7
Key Value
--- -----
approved true
$ vault write sys/control-group/request accessor=bbb4deef-e06d-9b2a-64a9-56f815c69ee7Key Value--- -----approved falseauthorizations <nil>request_entity map[name:Bob Smith id:38700386-723d-3d65-43b7-4fb44d7e6c30]request_path EU_GDPR_data/orders/acct1
$ vault write sys/control-group/authorize accessor=bbb4deef-e06d-9b2a-64a9-56f815c69ee7Key Value--- -----approved true
Now, the approved
status is true
.
-
Since the control group requires one approval from a member of acct_manager
group, the condition has been met. Log back in as bob
and unwrap the secret.
Example:
$ vault login -method=userpass username="bob" password="training"
$ vault unwrap 1f1411bc-2f18-551a-5e58-0fe44432e9a5
Key Value
--- -----
refresh_interval 768h
order_number 12345678
product_id 987654321
$ vault login -method=userpass username="bob" password="training"
$ vault unwrap 1f1411bc-2f18-551a-5e58-0fe44432e9a5Key Value--- -----refresh_interval 768horder_number 12345678product_id 987654321
»API call using cURL
-
Log in as bob
.
$ curl --request POST \
--data '{"password": "training"}' \
http://127.0.0.1:8200/v1/auth/userpass/login/bob | jq
$ curl --request POST \ --data '{"password": "training"}' \ http://127.0.0.1:8200/v1/auth/userpass/login/bob | jq
Copy the generated client_token
value.
-
Request to EU_GDPR_data/orders/acct1
:
$ curl --header "X-Vault-Token: <bob_client_token>" \
http://127.0.0.1:8200/v1/EU_GDPR_data/orders/acct1 | jq
{
...
"wrap_info": {
"token": "20a2f2b3-8bea-4e16-980b-82724dcdc38b",
"accessor": "9910cb38-600c-29d8-1c39-764a1c89a481",
"ttl": 86400,
"creation_time": "2018-08-08T10:13:06-07:00",
"creation_path": "EU_GDPR_data/orders/acct1"
},
...
}
$ curl --header "X-Vault-Token: <bob_client_token>" \ http://127.0.0.1:8200/v1/EU_GDPR_data/orders/acct1 | jq{ ... "wrap_info": { "token": "20a2f2b3-8bea-4e16-980b-82724dcdc38b", "accessor": "9910cb38-600c-29d8-1c39-764a1c89a481", "ttl": 86400, "creation_time": "2018-08-08T10:13:06-07:00", "creation_path": "EU_GDPR_data/orders/acct1" }, ...}
The response includes wrap_info
instead of the actual data.
Copy the accessor
value.
-
Now, a member of acct_manager
must approve this request. Log in as
ellen
who is a member of acct_manager
group.
$ curl --request POST \
--data '{"password": "training"}' \
http://127.0.0.1:8200/v1/auth/userpass/login/ellen | jq
$ curl --request POST \ --data '{"password": "training"}' \ http://127.0.0.1:8200/v1/auth/userpass/login/ellen | jq
Copy the generated client_token
value.
-
As a user, ellen
, you can check the current status and then authorize bob's
request. (NOTE: Be sure to replace <accessor>
with the accessor
value you
copied earlier.)
$ curl --header "X-Vault-Token: <ellen_client_token>" \
--request POST \
--data '{"accessor": "<accessor>"}' \
http://127.0.0.1:8200/v1/sys/control-group/request | jq
{
...
"data": {
"approved": false,
"authorizations": null,
"request_entity": {
"id": "38700386-723d-3d65-43b7-4fb44d7e6c30",
"name": "Bob Smith"
},
"request_path": "EU_GDPR_data/orders/acct1"
},
...
}
$ curl --header "X-Vault-Token: <ellen_client_token>" \
--request POST \
--data '{"accessor": "<accessor>"}' \
http://127.0.0.1:8200/v1/sys/control-group/authorize | jq
{
...
"data": {
"approved": true
},
...
}
$ curl --header "X-Vault-Token: <ellen_client_token>" \ --request POST \ --data '{"accessor": "<accessor>"}' \ http://127.0.0.1:8200/v1/sys/control-group/request | jq{ ... "data": { "approved": false, "authorizations": null, "request_entity": { "id": "38700386-723d-3d65-43b7-4fb44d7e6c30", "name": "Bob Smith" }, "request_path": "EU_GDPR_data/orders/acct1" }, ...}
$ curl --header "X-Vault-Token: <ellen_client_token>" \ --request POST \ --data '{"accessor": "<accessor>"}' \ http://127.0.0.1:8200/v1/sys/control-group/authorize | jq{ ... "data": { "approved": true }, ...}
Now, the approved
status is true
.
-
The bob
user should be able to unwrap the secrets.
$ curl --header "X-Vault-Token: <bob_client_token>" \
--request POST \
--data '{"token": "<wrapping_token>"}' \
http://127.0.0.1:8200/v1/sys/wrapping/unwrap | jq
{
...
"data": {
"order_number": "12345678",
"product_id": "987654321"
},
...
}
$ curl --header "X-Vault-Token: <bob_client_token>" \ --request POST \ --data '{"token": "<wrapping_token>"}' \ http://127.0.0.1:8200/v1/sys/wrapping/unwrap | jq{ ... "data": { "order_number": "12345678", "product_id": "987654321" }, ...}
»Web UI
The user, ellen
can approve the data access request via UI.
Open the Vault sign in page in a web browser (e.g.
http://127.0.0.1:8200/ui/vault/auth?with=userpass). In the Userpass tab,
enter ellen
in the Username field, and training
in the
Password field.
Click Sign in.
Select the Access tab, and then Control Groups.
Enter the wrapping_accessor
value in the Accessor field and click
Lookup. 
Awaiting authorization message displays. 
Click Authorize. The message changes to "Thanks! You have given
authorization."
Bob needs to request data access via CLI or API. Once the access request was
approved, use the CLI or API to unwrap the secrets.
»Step 5: ACL Policy vs. Sentinel Policy
Although the read-gdpr-order.hcl
was written as ACL policy, you
can implement Control Groups in either ACL or Sentinel policies.
Using Sentinel, the same policy may look something like:
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()
}
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/orders/*
" path.
Refer to the Sentinel
Properties
documentation for the list of available properties associated with control
groups.
»Next steps
To protect your secrets, it may become necessary to write finer-grained
policies to introspect different aspects of incoming requests. If you have not
already done so, read Sentinel
documentation to learn more about what you can accomplish writing policies as a
code.