Operations

[Enterprise] Control Groups

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.

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/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:

# Create and manage ACL policies
path "sys/policies/acl/*"
{
  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}

# To enable secret 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" ]
}

Steps

The scenario in this guide 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.

Scenario

You are going to perform the following:

  1. Implement a control group
  2. Deploy the policies
  3. Setup entities and a group
  4. Verification
  5. ACL Policies vs. Sentinel Policies

Step 1: Implement a control group

Author a policy named read-gdpr-order.hcl.

Bob needs read permissions on EU_GDPR_data/data/orders/*:

path "EU_GDPR_data/data/orders/*" {
  capabilities = [ "read" ]
}

Now, add control group to this policy:

path "EU_GDPR_data/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.

# 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"]
}

Step 2: Deploy the policies

Deploy the read-gdpr-order and acct_manager policies that you wrote.

CLI command / API call using cURL / Web UI

CLI command

# Create read-gdpr-order policy
$ vault policy write read-gdpr-order read-gdpr-order.hcl

# Create acct_manager policy
$ vault policy write acct_manager acct_manager.hcl

API call using cURL

# Construct API request payload to create read-gdpr-read policy
$ tee payload-1.json <<EOF
{
  "policy": "path \"EU_GDPR_data/data/orders/*\" {\n  capabilities = [ \"read\" ]\n\n  control_group = {\n    factor \"authorizer\" {\n        identity {\n            group_names = [ \"acct_manager\" ]\n            approvals = 1\n        }\n    }\n  }\n}"
}
EOF

# Create read-gdpr-order policy
$ curl --header "X-Vault-Token: ..." \
       --request PUT \
       --data @payload-1.json \
       http://127.0.0.1:8200/v1/sys/policies/acl/read-gdpr-order

# Construct API request payload to create acct_manager policy
$ tee payload-2.json <<EOF
{
 "policy": "# To approve the request\npath \"sys/control-group/authorize\" {\n    capabilities = [\"create\", \"update\"]\n}\n\n# To check control group request status\npath \"sys/control-group/request\" {\n    capabilities = [\"create\", \"update\"]\n}"
}
EOF

# Create acct_manager policy
$ 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.

  1. Click the Policies tab, and then select Create ACL policy.

  2. Toggle Upload file, and click Choose a file to select your read-gdpr-order.hcl file you authored at Step 1. Create
Policy This loads the policy and sets the Name to be read-gdpr-order.

  3. Click Create Policy to complete.

  4. Repeat the steps to create a policy for acct_manager.

Step 3: Setup entities and a group

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.

CLI command / Web UI

CLI command

The following command uses jq tool to parse JSON output.

# Enable userpass
$ vault auth enable userpass

# Create a user, bob
$ vault write auth/userpass/users/bob password="training"

# Create a user, ellen
$ vault write auth/userpass/users/ellen password="training"

# 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 the entity_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)

# Create Ellen Wright entity and save the identity ID in the entity_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)

# Finally, create acct_manager group and add Ellen Wright entity as a member
$ vault write identity/group name="acct_manager" \
      policies="acct_manager" \
      member_entity_ids=$(cat entity_id_ellen.txt)

Web UI

  1. Click the Access tab, and select Enable new method.

  2. Select Username & Password and click Next.

  3. Click Enable Method.

  4. Click the Vault CLI shell icon (>_) to open a command shell. Execute vault write auth/userpass/users/bob password=training to create a new user bob. Create Policy

  5. Enter vault write auth/userpass/users/ellen password=training to create a new user ellen.

  6. Click the icon (>_) again to hide the shell.

  7. From the Access tab, select Entities and then Create entity.

  8. Populate the Name, Policies and Metadata fields as shown below. Create Entity

  9. Click Create.

  10. Select Create alias. Enter bob in the Name field and select userpass/ (userpass) from the Auth Backend drop-down list.

  11. Click Create.

  12. Return to the Entities tab and then Create entity.

  13. Populate the Name, Policies and Metadata fields as shown below. Create Entity

  14. Click Create.

  15. Select Create alias. Enter ellen in the Name field and select userpass/ (userpass) from the Auth Backend drop-down list.

  16. Click Create.

  17. Click Groups from the left navigation, and select Create group.

  18. Enter acct_manager in the Name, and again enter acct_manager in the Policies fields.

  19. In the Member Entity IDs field, select Ellen Wright and then click Create.

Step 4: Verification

Now, let's see how the control group works.

CLI command / API call using cURL / Web UI

CLI Command

  1. First, enable the key/value secrets engine at EU_GDPR_data and write some mock data:

    # Enable kv-v1 at EU_GDPR_data
    $ vault secrets enable -path=EU_GDPR_data -version=2 kv
    
    # Write some mock data
    $ vault kv put EU_GDPR_data/orders/acct1 order_number="12345678" product_id="987654321"
    
  2. Log in as bob.

    $ vault login -method=userpass username="bob" password="training"
    
  3. Request to read "EU_GDPR_data/orders/acct1":

    $ vault kv get EU_GDPR_data/orders/acct1
    
    Key                              Value
    ---                              -----
    wrapping_token:                  s.h9jGEBexp0o7vQhmImZKAAKo
    wrapping_accessor:               HDUlEfkrbrfPOlfUnwgar025
    wrapping_token_ttl:              24h
    wrapping_token_creation_time:    2019-05-10 16:38:36 -0700 PDT
    wrapping_token_creation_path:    EU_GDPR_data/data/orders/acct1
    

    The response includes wrapping_token and wrapping_accessor. Copy this wrapping_accessor value.

  4. 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"
    
  5. As a user, ellen, you can check and authorize bob's request using the following commands.

    # To check the current status
    $ vault write sys/control-group/request accessor=<wrapping_accessor>
    
    # To approve the request
    $ vault write sys/control-group/authorize accessor=<wrapping_accessor>
    

    Example:

    # Check the current status
    $ vault write sys/control-group/request accessor=HDUlEfkrbrfPOlfUnwgar025
    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
    
    # Approve the request
    $ vault write sys/control-group/authorize accessor=HDUlEfkrbrfPOlfUnwgar025
    Key         Value
    ---         -----
    approved    true
    

    Now, the approved status is true.

  6. 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:

    # Log back in as bob - you can use the bob's token: vault login <bob_token>
    $ vault login -method=userpass username="bob" password="training"
    
    # Unwrap the secrets by passing the wrapping_token
    $ vault unwrap s.h9jGEBexp0o7vQhmImZKAAKo
    
    Key                 Value
    ---                 -----
    refresh_interval    768h
    order_number        12345678
    product_id          987654321
    

API call using cURL

  1. First, enable the key/value secrets engine at EU_GDPR_data and write some mock data:

    # Create the request payload
    $ tee payload.json <<EOF
    {
      "type": "kv",
      "options": {
          "version": "2"
      }
    }
    EOF
    
    # Enable K/V v2 at EU_GDPR_data
    $ curl --header "X-Vault-Token: ..." \
           --request POST \
           --data @payload.json \
           https://127.0.0.1:8200/v1/sys/mounts/EU_GDPR_data
    
    # Write some mock data
    $ tee test_data.json <<EOF
    {
      "data": {
         "order_number": "12345678",
         "product_id": "987654321"
      }
    }
    EOF
    
    $ curl --header "X-Vault-Token: ..." \
           --request POST \
           --data @test_data.json \
           https://127.0.0.1:8200/v1/EU_GDPR_data/data/orders/acct1 | jq
    
  2. Log in as bob.

    $ curl --request POST \
           --data '{"password": "training"}' \
           http://127.0.0.1:8200/v1/auth/userpass/login/bob | jq
    
  3. Copy the generated client_token value.

  4. Request to EU_GDPR_data/data/orders/acct1:

    $ curl --header "X-Vault-Token: <bob_client_token>" \
           http://127.0.0.1:8200/v1/EU_GDPR_data/data/orders/acct1 | jq
    {
       ...
       "wrap_info": {
          "token": "s.IeWHTXLT86tYLmBAmoilcsht",
          "accessor": "3yqcPuNuafwMXshAnrNZ39bP",
          "ttl": 86400,
          "creation_time": "2019-05-10T17:15:18-07:00",
          "creation_path": "EU_GDPR_data/data/orders/acct1"
        },
       ...
    }
    

    The response includes wrap_info instead of the actual data. Copy the accessor value.

  5. 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
    
  6. Copy the generated client_token value.

  7. As a user, ellen, you can check the current status and then authorize bob's request.

    # To check the current status using sys/control-group/request endpoint
    $ 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": "be186bb9-0927-f318-b65f-ee831ef69e66",
            "name": "Bob Smith"
          },
          "request_path": "EU_GDPR_data/data/orders/acct1"
        },
       ...
    }
    
    # Now, authorize the request using sys/control-group/authorize endpoint
    $ 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.

  8. The bob user should be able to unwrap the secrets. Be sure to replace <wrapping_token> with the token value you received in the response under the wrap_info block.

    $ 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

  1. Open the Vault sign in page in a web browser (e.g. http://127.0.0.1:8200/ui/vault/auth?with=userpass). Select Username from the Method drop-down list, enter bob in the Username field, and training in the Password field.

  2. Click Sign in.

  3. Click the Vault CLI shell icon (>_) to open a command shell. Execute vault read EU_GDPR_data/data/orders/acct1: Control Groups

  4. Copy the Control Group Token and Accessor values and paste them in a text editor. You will need these values later.

  5. Sign out of the UI.

  6. Now, sign in with username ellen and password training.

  7. Select the Access tab, and then Control Groups.

  8. Enter the Accessor value you copied in the Accessor field and click Lookup.

    Awaiting authorization message displays. Control
Groups

  9. Click Authorize. The message changes to "Thanks! You have given authorization." Control Groups

  10. Sign out of the UI.

  11. Now, sign in with username bob and password training.

  12. Select the Tools tab and then Unwrap.

  13. Enter the Control Group Token value you copied earlier in the Wrapping token field. Control Groups

  14. Click Unwrap data to read 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()
}

Deploy this policy as an Endpoint Governing Policy attached to "EU_GDPR_data/data/orders/*" path.

Help and Reference