Skip to content

Instantly share code, notes, and snippets.

@mdasilva
Created May 11, 2018 15:05
Show Gist options
  • Save mdasilva/2a4023afb612eb7d2178ea2cf708c279 to your computer and use it in GitHub Desktop.
Save mdasilva/2a4023afb612eb7d2178ea2cf708c279 to your computer and use it in GitHub Desktop.

Vault Secret Management

Vault is a tool for securely accessing secrets. In this guide we'll be standing up development instances of Vault and Consul to illustrate features such as;

  • Dynamic secret generation
  • LDAP authentication
  • Policy based authorization

Before you get started

This guide assumes familiarity with command line execution, making HTTP API requests, and basic Docker usage.

Command line examples are taken from a bash shell. Adjust as necessary for your platform. The $ is used to denote command execution and set it apart from it's output. Exclude it when running the commands.

Please have the following available on your system before you begin.

While Docker is not required for the early parts of this guide, it will be required to setup an LDAP server to demonstrate LDAP authentication, and when extending this setup for later demonstrations.

If you have Docker, start by creating an overlay network for our services. Let's call it hashicorp.

Create an overlay network (if using Docker)

$ docker network create hashicorp

Setting up Consul

Consul is a distributed key-value (kv) store. It has many components to extend it's kv store to multiple use cases, such as; service discovery, health checking, and dynamic configuration. You can read more about it here.

For this guide, Consul will be used to demonstrate Vault's ability to generate dynamic secrets. The generated tokens and their privileges will be based on group membership of the requesting user.

Dynamic secrets are generated when they are a requested and have a limited duration in which they are valid. This Secrets as a Service model simplifies key-rolling and the built-in revoation limits exposure to lost or stolen secrets.

Let's start by bringing up a single Consul server in development mode.

WARNING: This section will setup a single node Consul cluster in development mode. Development mode does not persist any data upon shutdown. It is meant for quick prototyping. It is not to be used in production.

Create a Consul configuration file

Create a file called consul-config.json with the following contents

{
    "datacenter": "dc1",
    "acl_datacenter": "dc1",
    "acl_master_token": "root",
    "acl_agent_token": "root",
    "acl_default_policy": "deny",
    "acl_down_policy": "extend-cache"
}

This configuration will enable Access Control Lists (ACL) and set root as a mater token. For production deployments, the master token should be complex and kept in a safe location. You can read more about Consul configuration here

Create a Consul server

Using Docker

$ docker run -d --name consul-server --network hashicorp -v $PWD/consul-config.json:/consul/config/server.json -p 8500:8500 -p 8600:8600/udp consul 

Without Docker

$ consul agent -dev -config-file=consul-config.json

Set some environment variable to be used when making Consul requests

$ export CONSUL_HTTP_ADDR=http://127.0.0.1:8500
$ export CONSUL_HTTP_TOKEN=root

Create a Consul management token for Vault

$ curl -XPUT --data '{ "Name": "vault", "Type": "management" }' "$CONSUL_HTTP_ADDR/v1/acl/create?token=$CONSUL_HTTP_TOKEN"
{
    "ID": "b23eeb23-2f28-8469-83d5-fba99b728f9a"
}

This token will be used in the next section to allow Vault to dynamically create Consul authentication tokens.


Setting up Vault

Create a Vault configuration file

Create a file called vault-config.json with the following contents

Using Docker

storage "consul" {
  address = "consul-server:8500",
  token = "b23eeb23-2f28-8469-83d5-fba99b728f9a",
  path = "vault",
  scheme = "http"
}

Without Docker

disable_mlock = true
storage "consul" {
    address = "127.0.0.1:8500",
    token = "b23eeb23-2f28-8469-83d5-fba99b728f9a",
    path = "vault",
    scheme = "http"
}

token is the Consul management token created in the earlier Setup Consul section.

For production deployments, scheme should be set to https for secure communication between Vault and Consul.

Start a Vault server in development mode

Using Docker

$ docker run -d --name vault-server --network hashicorp --cap-add=IPC_LOCK -v $PWD/vault-config.json:/vault/config/server.json -p 8200:8200 vault

Without Docker

$ vault server -dev -config=vault-config.json

Get the Root Token

During initial startup, Vault will display its Unseal Key and Root Token.

The Unseal Key is required to decrypt the Vault instance itself. A sealed Vault is completely inaccessible until unsealed. Suffice to say all data contained within a Vault will be irrovocably lost without the Unseal Key(s).

The Root Token is the master access token which has full privileges. The Root Token should only be used during initial setup in order to create additional privileged keys.

It is critical that both the Unseal Key(s) and Root Token are kept in a safe location for production deployments.

For this guide, we have started our Vault server in development mode which, in addition to skipping some initial setup, automatically starts the service with an unsealed vault. For this reason, we will only need the Root Token.

Using Docker

$ docker logs vault-server 

If you started Vault without Docker, the Root Token will be displayed during service startup.

Set some environment variable for reference at a later time

$ export VAULT_ADDR=http://127.0.0.1:8200
$ export VAULT_ROOT_TOKEN=MY_VAULT_ROOT_TOKEN_VALUE

Authenticate to Vault

Authenticate with the Root Token for initial configuration

$ vault login $VAULT_ROOT_TOKEN
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                Value
---                -----
token              f10c32be-690f-3ff3-70e0-6cc3b289cb5a
token_accessor     f4c3b0e3-4b41-9404-a9b3-914405b7373d
token_duration     ∞
token_renewable    false
token_policies     [root]

Enable Consul secret engine

Dynamic secrets can be enabled for services with supported secret engines. A full list can be seen here

$ vault secrets enable consul
Success! Enabled the consul secrets engine at: consul/

Configure Consul secret engine

$ vault write consul/config/access address=consul-server:8500 token=b23eeb23-2f28-8469-83d5-fba99b728f9a
Success! Data written to: consul/config/access

If you are not using Docker, replace consul-server with 127.0.0.1:8500 The token value is the Consul managemnt token created in the earlier section.

Create Consul roles

We'll be creating two roles, sysadmins and developers, which will map to the authenticating user's group membership. The policy will be attached to the dynamically created Consul authentication token to assign privilieges. You can read more on Consul ACL system here.

Create a sysadmins role with write access to any key in Consul.

$ vault write consul/roles/sysadmins policy=$(echo -n 'key "" { policy = "write" }' | base64 -)
Success! Data written to: consul/roles/sysadmins

Create a developers role with read access to any key in Consul.

$ vault write consul/roles/developers policy=$(echo -n 'key "" { policy = "read" }' | base64 -)
Success! Data written to: consul/roles/developers

Testing the setup so far

Verify that the Consul secret engine is working

Request a Consul authentication token for both the developers and sysadmins roles.

$ vault read consul/creds/developers
Key                Value
---                -----
lease_id           consul/creds/developers/676dc883-3587-e41e-61bc-0c1879150483
lease_duration     768h
lease_renewable    true
token              4a604fd2-bc96-a5d4-1d93-1ab6fc79d10e

$ vault read consul/creds/sysadmins
Key                Value
---                -----
lease_id           consul/creds/sysadmins/3ff82430-c099-ae38-b09f-06c444ae5ca0
lease_duration     768h
lease_renewable    true
token              3ca6576c-bd92-7a08-1f08-f18c3703949c 

Notice that the tokens are created with a lease to limit the valid period of each token. Upon expiry, Vault will automatically revoke the tokens in Consul. Read more about token leases here.

Verify the authentication tokens were created in Consul

$ curl -s "$CONSUL_HTTP_ADDR/v1/acl/list?token=$CONSUL_ROOT_TOKEN"
[
    {
        "ID": "3ca6576c-bd92-7a08-1f08-f18c3703949c",
        "Name": "Vault sysadmins root 1526043919535409250",
        "Type": "client",
        "Rules": "key \"\" { policy = \"write\" }",
        "CreateIndex": 267,
        "ModifyIndex": 267
    },
    {
        "ID": "4a604fd2-bc96-a5d4-1d93-1ab6fc79d10e",
        "Name": "Vault developers root 1526043718144862257",
        "Type": "client",
        "Rules": "key \"\" { policy = \"read\" }",
        "CreateIndex": 252,
        "ModifyIndex": 252
    },
    {
        "ID": "anonymous",
        "Name": "Anonymous Token",
        "Type": "client",
        "Rules": "",
        "CreateIndex": 4,
        "ModifyIndex": 4
    },
    {
        "ID": "b23eeb23-2f28-8469-83d5-fba99b728f9a",
        "Name": "vault",
        "Type": "management",
        "Rules": "",
        "CreateIndex": 21,
        "ModifyIndex": 21
    },
    {
        "ID": "root",
        "Name": "Master Token",
        "Type": "management",
        "Rules": "",
        "CreateIndex": 5,
        "ModifyIndex": 5
    }
]

Next, take the sysadmins and developers authentication token and verify its Consul ACL priviliges by reading and writing a value into Consul. This can be done through the Consul HTTP API or with the Consul CLI. I'll list both as a brief example.

Verify the developers token cannot write a key

Using the HTTP API

$ curl -s -XPUT --data 'http://www.google.ca' "$CONSUL_HTTP_ADDR/v1/kv/proxies/webproxy01?token=4a604fd2-bc96-a5d4-1d93-1ab6fc79d10e"
Permission denied

Using the Consul command

$ consul kv put -token=4a604fd2-bc96-a5d4-1d93-1ab6fc79d10e  proxies/webproxy01 http://www.google.ca
Error! Failed writing data: Unexpected response code: 403 (Permission denied)

Verify the sysadmins token can write a key

Using the HTTP API

$ curl -s -XPUT --data 'http://www.google.ca' "$CONSUL_HTTP_ADDR/v1/kv/proxies/webproxy01?token=3ca6576c-bd92-7a08-1f08-f18c3703949c"
true

Using the Consul command

$ consul kv put -token=3ca6576c-bd92-7a08-1f08-f18c3703949c proxies/webproxy01 http://www.google.ca
Success! Data written to: proxies/webproxy01

Verify the developers token can read a key

Using the HTTP API

$ curl "$CONSUL_HTTP_ADDR/v1/kv/proxies/webproxy01?token=4a604fd2-bc96-a5d4-1d93-1ab6fc79d10e"
[
    {
        "LockIndex": 0,
        "Key": "proxies/webproxy01",
        "Flags": 0,
        "Value": "aHR0cDovL3d3dy5nb29nbGUuY2E=",
        "CreateIndex": 322,
        "ModifyIndex": 322
    }
]

Using the Consul command

$ consul kv get -token=4a604fd2-bc96-a5d4-1d93-1ab6fc79d10e proxies/webproxy01
http://www.google.ca

Vault LDAP Authentication

So far we've stood up distributed kv store (Consul) and means to dynamically provision role based access (Vault). Next we'll focus on authentication, mapping users to the sysadmins and developers roles we've previously created.

We'll accomplish this by standing up an OpenLDAP server, seeding it with some user accounts and groups, and connecting it to Vault.

In this section we'll be using Docker and the osixia/openldap image to quickly start the OpenLDAP server. If you do not have Docker available on your system you can install it by going here, otherwise you may use another available LDAP service and make adjustments to this guide as needed.

Create some users and groups to seed our LDAP server

Create a seed.ldif file with the following contents

dn: ou=users,dc=example,dc=org
changeType: add
objectClass: organizationalUnit
description: Organization users
ou: users

dn: ou=groups,dc=example,dc=org
changeType: add
objectClass: organizationalUnit
description: Organization groups
ou: groups

dn: cn=sysadmins,ou=groups,dc=example,dc=org
changeType: add
objectClass: groupOfUniqueNames
description: System Admin role
cn: sysadmins
uniqueMember: uid=dfayden,ou=users,dc=example,dc=org
uniqueMember: uid=etirel,ou=users,dc=example,dc=org

dn: cn=developers,ou=groups,dc=example,dc=org
changeType: add
objectClass: groupOfUniqueNames
description: Developer role
cn: developers
uniqueMember: uid=gjura,ou=users,dc=example,dc=org
uniqueMember: uid=jbeleren,ou=users,dc=example,dc=org

dn: uid=dfayden,ou=users,dc=example,dc=org
changeType: add
objectClass: inetOrgPerson
description: Self-proclaimed "greatest thief in the Multiverse."
sn: Fayden
givenName: Dack
cn: Dack Fayden
uid: dfayden
mail: dfayden@example.org
memberOf: cn=sysadmins,ou=groups,dc=example,dc=org
userPassword: dfayden!

dn: uid=etirel,ou=users,dc=example,dc=org
changeType: add
objectClass: inetOrgPerson
description: Left her embattled homeland in search of a new place to call home.
sn: Tirel
givenName: Elspeth
cn: Elspeth Tirel
uid: etirel
mail: etirel@example.org
memberOf: cn=sysadmins,ou=groups,dc=example,dc=org
userPassword: etirel!

dn: uid=gjura,ou=users,dc=example,dc=org
changeType: add
objectClass: inetOrgPerson
description: Fiercely loyal, unyielding, just and charismatic.
sn: Jura
givenName: Gideon
cn: Gideon Jura
uid: gjura
mail: gjura@example.org
memberOf: cn=developers,ou=groups,dc=example,dc=org
userPassword: gjura!

dn: uid=jbeleren,ou=users,dc=example,dc=org
changeType: add
objectClass: inetOrgPerson
description: Brilliant, curious, and always in control.
sn: Beleren
givenName: Jace
cn: Jace Beleren
uid: jbeleren
mail: jbeleren@example.org
memberOf: cn=developers,ou=groups,dc=example,dc=org
userPassword: jbeleren!

Start an OpenLDAP server

$ docker run -d --name ldap-server --network hashicorp -v $PWD/seed.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/seed.ldif osixia/openldap:1.2.0 --copy-service

Perform an LDAP query to verify the OpenLDAP server is working

$ docker exec ldap-server ldapsearch -x -H ldap://localhost -b dc=example,dc=org -D "cn=admin,dc=example,dc=org" -w admin

Enable LDAP authentication in Vault

$ vault auth enable ldap
Success! Enabled ldap auth method at: ldap/

Configure LDAP authentication in Vault

$ vault write auth/ldap/config \
url="ldap://ldap-server" \
userdn="ou=users,dc=example,dc=org" \
userattr="uid" \
groupdn="ou=groups,dc=example,dc=org" \
groupfilter="(&(objectClass=groupOfUniqueNames)(uniqueMember={{.UserDN}}))" \
groupattr="cn" \
binddn="cn=admin,dc=example,dc=org" \
bindpass="admin"

Success! Data written to: auth/ldap/config

Creating Vault policies

Next we'll be creating two Vault policies, consul-devs and consul-sysadmins to control what type of authentication token can be requested.

Create a file called consul-devs.json with the following contents

path "consul/creds/developers" {
  capabilities = ["read"]
}

Create a file called consul-sysadmins.json with the following contents

path "consul/creds/sysadmins" {
  capabilities = ["read"]
}

Add the policies in Vault

$ vault policy write consul-devs /tmp/consul-devs.json
Success! Uploaded policy: consul-devs

$ vault policy write consul-sysadmins /tmp/consul-devs.json
Success! Uploaded policy: consul-sysadmins

Link LDAP groups to Vault policies

$ vault write auth/ldap/groups/developers policies=consul-devs
Success! Data written to: auth/ldap/groups/developers

$ vault write auth/ldap/groups/sysadmins policies=consul-sysadmins
Success! Data written to: auth/ldap/groups/sysadmins

Testing LDAP authentication

Login as a user with developer membership

$ vault login -method=ldap username=gjura password=gjura!
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                    Value
---                    -----
token                  955524f4-b3b8-b9da-03ab-f5789f462c2b
token_accessor         65269a37-f826-0abb-f691-f6c281fa316e
token_duration         768h
token_renewable        true
token_policies         [consul-devs default]
token_meta_username    gjura

A few things to note;

  • Vault login tokens are also controlled by a lease to force users to reauthenticate periodically.
  • The token is the primary authentication token for the user, and is used to perform Vault actions allowed by their token_policies.
  • The token_accessor is a special authentication token that only allows limited actions; 1) looking up token properties and 2) revoking the token. This is useful for services leveraging Vault to simply revoke user tokens without requiring high level Vault privilegs.

Testing the policies

Test the consul-devs policy by requesting a developers and sysadmins Consul authentication token

$ vault read consul/creds/developers
Key                Value
---                -----
lease_id           consul/creds/developers/afbb7a27-efc6-5199-9f15-f3df274c82ab
lease_duration     768h
lease_renewable    true
token              afd34e9a-ac88-58ca-45c2-8dd1172daf40

$ vault read consul/creds/sysadmins
Error reading consul/creds/sysadmins: Error making API request.

URL: GET http://127.0.0.1:8200/v1/consul/creds/sysadmins
Code: 403. Errors:

* permission denied

Verify that the generated developer token has the correct Consul ACL applied; read access, but no write access.

$ curl -s "$CONSUL_HTTP_ADDR/v1/kv/proxies/?token=afd34e9a-ac88-58ca-45c2-8dd1172daf40&recurse=true"
[
    {
        "LockIndex": 0,
        "Key": "proxies/webproxy01",
        "Flags": 0,
        "Value": "aHR0cDovL3d3dy5nb29nbGUuY2E=",
        "CreateIndex": 322,
        "ModifyIndex": 322
    }
]

$ curl -s -XPUT --data 'http://www.bing.com' "$CONSUL_HTTP_ADDR/v1/kv/proxies/webproxy02?token=afd34e9a-ac88-58ca-45c2-8dd1172daf40"
Permission denied

Perform the same tests to verify the consul-sysadmins policy after logging in as a user that is a member of the sysadmins group.

Additional Reading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment