Skip to content

Some integration patterns using Vault AppRole authentication and Chef

Notifications You must be signed in to change notification settings

soakes/vault-approle-chef

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 

Repository files navigation

Integration Pattern: Vault AppRole and Chef Example

Overview

The purpose of using Vault's AppRole backend to to split up the values needed for an authentication and deliver them through two different channels to prevent any one system, other than the target client, to be in possession of the full set of credentials. In this way, we're able to provide narrowly-scoped tokens to these channels (e.g. Packer, Terraform, Chef, or other provisioning, orchestration, or configuration management tools. The narrowly-scoped tokens only have permissions to retrieve either the role_id or secret_id values from Vault (but not both).

This document outlines the steps to set up an AppRole authentication backend in Vault and demonstrates how to utilize the backend, both directly via the Vault CLI as well as through the HTTP API.

In addition, we'll go over a best-practice pattern for using the AppRole backend to enable integration between Vault and a configuration management s tool (Chef) making use of the Vault Ruby Gem.

We'll be using the application example-app as the basis for this demonstration.

Quick Start

To speed through the below steps and create a functioning AppRole backend to use with other examples, we can simply run the following commands.

First, let's start vault in -dev mode and push it into the background so we can use the same terminal:

VAULT_REDIRECT_ADDR=http://127.0.0.1:8200 && VAULT_UI=true && vault server -log-level=TRACE -dev -dev-root-token-id=root -dev-listen-address="0.0.0.0:8200" &>/dev/null &

We can use the jobs command to view the status of background jobs and fg to bring a job to the foreground.

Next, we'll login with the root token and set up our AppRole:

# Login with our dev root token
vault auth root

# Dummy data
vault write secret/example-app foo=bar

# Example-app policy
tee example-app-policy.hcl <<EOF
path "secret/example-app/*" {
  capabilities = ["list", "read"]
}
EOF

# Write the policy to Vault
vault write sys/policy/example-app-policy [email protected]

# Mount the AppRole auth backend
vault auth-enable approle

# Configure the example-app AppRole role
vault write auth/approle/role/example-app \
    secret_id_ttl=10m \
    token_num_uses=10 \
    token_ttl=20m \
    token_max_ttl=30m \
    secret_id_num_uses=1 \
    policies=example-app-policy

You can now continue with any AppRole examples. Read the below steps to understand the details behind this quick start.

Initial Setup

Start by logging into Vault with the root token (or an administrative user). In order to make using the HTTP API a bit easier, make sure that the VAULT_ADDR and VAULT_TOKEN environment variables are set:

export VAULT_ADDR=http://127.0.0.1:8200

# Assuming you authenticated already via the Vault CLI
export VAULT_TOKEN=$(cat ~/.vault-token)

# Provide the authentication token directly
export VAULT_TOKEN=root

Also, the HTTP API responses are provided in raw JSON output, which isn't the easiest to read. In order to make this output more readable, we'll pipe output to the jq tool where appropriate. For example:

curl -s \
  --header "X-Vault-Token: $VAULT_TOKEN" \
  $VAULT_ADDR/v1/auth/approle/role/example-app/role-id | jq

# Output
{
  "request_id": "264231be-2109-f6bb-f317-402f2c742c0c",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "role_id": "b0559830-1fc9-a751-e866-ff04f9dbe58e"
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}

Before we start with the AppRole configuration, let's create some dummy data that will be scoped to our example-app. We will also define a policy against that data.

The purpose of this policy is to restrict tokens generated by our example-app AppRole configuration to only be able to retrieve secrets from this path.

CLI

vault write secret/example-app foo=bar

API

tee payload.json <<EOF
{
  "foo": "bar"
}
EOF

curl \
  --header "X-Vault-Token: $VAULT_TOKEN" \
  --request POST \
  --data @payload.json \
  $VAULT_ADDR/v1/secret/example-app

Define our example-app policy to read from this path and write the policy to Vault:

CLI

tee example-app-policy.hcl <<EOF
path "secret/example-app" {
  capabilities = ["list", "read"]
}
EOF

vault write sys/policy/example-app-policy [email protected]

API

tee payload.json <<EOF
{
  "rules": "path \"secret/example-app/*\" { capabilities = [\"list\", \"read\"] }"
}
EOF

curl \
  --header "X-Vault-Token: $VAULT_TOKEN" \
  --request POST \
  --data @payload.json \
  $VAULT_ADDR/v1/sys/policy/example-app-policy

AppRole Setup

With the initial setup complete, let's now mount an AppRole backend:

CLI

vault auth-enable approle

API

tee payload.json <<EOF
{
  "type": "approle"
}
EOF

curl \
  --header "X-Vault-Token: $VAULT_TOKEN" \
  --request POST \
  --data @payload.json \
  $VAULT_ADDR/v1/sys/auth/approle

Next, we will create a role within the AppRole backend for our example-app. A role defines a number of different parameters that are applied to the tokens and secret_id values generated for that role. It also allows us to define the policies to attach (e.g. our example-app-policy from above):

CLI

vault write auth/approle/role/example-app \
    secret_id_ttl=10m \
    token_num_uses=10 \
    token_ttl=20m \
    token_max_ttl=30m \
    secret_id_num_uses=1 \
    policies=example-app-policy

API

tee payload.json <<EOF
{
  "secret_id_ttl": "10m",
  "token_num_uses": "10",
  "token_ttl": "20m",
  "token_max_ttl": "30m",
  "secret_id_num_uses": "1",
  "policies": [ "example-app-policy" ]
}
EOF

curl \
  --header "X-Vault-Token: $VAULT_TOKEN" \
  --request POST \
  --data @payload.json \
  $VAULT_ADDR/v1/auth/approle/role/example-app

For a full list of available parameters and their use, refer to this documentation: AppRole Auth Backend - HTTP API - Vault by HashiCorp

Interacting With Our AppRole Role

Now that we have created our example-app AppRole configuration, lets see how we can interact with it.

The two values required to perform an AppRole authentication are role_id and secret_id . These are similar to the username (or email address) and password approach for authentication.

The role_id is not considered to be a secret value; rather, it's a static UUID that identifies a specific role configuration.

The secret_id, on the other hand, should be handled as you would a sensitive value and will be unique to each instance of our example-app.

RoleID

Two common patterns is for the role_id value to be embedded into a machine image or container as a text file or environment variable (e.g. using Packer or delivered during the provisioning process using Terraform. There are a number of different patterns through which this value can be delivered.

The important concept to remember is that reading the role_id will return the a static UUID for a defined AppRole role; it does not change every time we retrieve the vault (whereas the secret_id does). In our example, all example-app instances would use the same role_id to perform an AppRole authentication.

Let's read the role_id for this AppRole:

CLI

vault read auth/approle/role/example-app/role-id

# Output
Key    	Value
---    	-----
role_id	b0559830-1fc9-a751-e866-ff04f9dbe58e

API

curl -s \
  --header "X-Vault-Token: $VAULT_TOKEN" \
  $VAULT_ADDR/v1/auth/approle/role/example-app/role-id | jq

# Output
{
  "request_id": "264231be-2109-f6bb-f317-402f2c742c0c",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "role_id": "b0559830-1fc9-a751-e866-ff04f9dbe58e"
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}

SecretID (Pull Mode)

The next value needed for an AppRole authentication is the secret_id. There are two modes of operation when working with the SecretID: Pull and Push.

In the Pull mode, Vault generates a new secret_id for every request. We can think of this mode as one where the client application "pulls" the value from Vault:

CLI

vault write -f /auth/approle/role/example-app/secret-id

# Output
Key               	Value
---               	-----
secret_id         	d3cbe550-da0a-17e6-d2ec-bba911649e1f
secret_id_accessor	24975928-dcd8-4fb4-7805-76ff70c4af23

API

curl -s \
  --header "X-Vault-Token: $VAULT_TOKEN" \
  --request POST \
  $VAULT_ADDR/v1/auth/approle/role/example-app/secret-id | jq

# Output
{
  "request_id": "e1505c2c-e0e1-9d5f-c546-93689ef8156d",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "secret_id": "f0c5ba2d-1768-bd1a-ffa9-2313d02a7d9d",
    "secret_id_accessor": "5ab186ca-02b1-b6bc-c09f-a06b649deaa5"
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}

This also returns a secret_id_accessor that can be used to read the properties of the secret_id without actually exposing the sensitive value:

CLI

vault write /auth/approle/role/example-app/secret-id-accessor/lookup secret_id_accessor=24975928-dcd8-4fb4-7805-76ff70c4af23

# Output
Key               	Value
---               	-----
SecretIDNumUses   	0
cidr_list         	[]
creation_time     	2017-10-20T22:01:37.232257501Z
expiration_time   	2017-10-20T22:11:37.232257501Z
last_updated_time 	2017-10-20T22:01:37.232257501Z
metadata          	map[]
secret_id_accessor	24975928-dcd8-4fb4-7805-76ff70c4af23
secret_id_num_uses	1
secret_id_ttl     	600

The following warnings were returned from the Vault server:
* The field SecretIDNumUses is deprecated and will be removed in a future release; refer to secret_id_num_uses instead

API

tee payload.json <<EOF
{
  "secret_id_accessor": "5ab186ca-02b1-b6bc-c09f-a06b649deaa5"
}
EOF

curl -s \
  --header "X-Vault-Token: $VAULT_TOKEN" \
  --request POST \
  --data @payload.json \
  $VAULT_ADDR/v1/auth/approle/role/example-app/secret-id-accessor/lookup | jq

# Output
{
  "request_id": "791934ff-d6e9-d74d-4dc2-954201acd2db",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "SecretIDNumUses": 0,
    "cidr_list": [],
    "creation_time": "2017-10-21T02:26:51.862283327Z",
    "expiration_time": "2017-10-21T02:36:51.862283327Z",
    "last_updated_time": "2017-10-21T02:26:51.862283327Z",
    "metadata": {},
    "secret_id_accessor": "5ab186ca-02b1-b6bc-c09f-a06b649deaa5",
    "secret_id_num_uses": 1,
    "secret_id_ttl": 600
  },
  "wrap_info": null,
  "warnings": [
    "The field SecretIDNumUses is deprecated and will be removed in a future release; refer to secret_id_num_uses instead"
  ],
  "auth": null
}

Now that we have our role_id and secret_id, we can perform our AppRole authentication to obtain our final token:

CLI

vault write auth/approle/login \
    role_id=b0559830-1fc9-a751-e866-ff04f9dbe58e \
    secret_id=d3cbe550-da0a-17e6-d2ec-bba911649e1f

# Output
Key                 	Value
---                 	-----
token               	fe7a46c2-5fdb-f373-21b3-78bdbd4f9261
token_accessor      	0639fd93-9af8-cf8d-3e0f-9ba44c8dff65
token_duration      	20m0s
token_renewable     	true
token_policies      	[default example-app-policy]
token_meta_role_name	"example-app"

API

tee payload.json <<EOF
{
  "role_id": "b0559830-1fc9-a751-e866-ff04f9dbe58e",
  "secret_id": "f0c5ba2d-1768-bd1a-ffa9-2313d02a7d9d"
}
EOF

curl -s \
  --request POST \
  --data @payload.json \
  $VAULT_ADDR/v1/auth/approle/login | jq

# Output
{
  "request_id": "b1ddf818-b9e4-637c-8e3a-424bb84e8d47",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": null,
  "wrap_info": null,
  "warnings": null,
  "auth": {
    "client_token": "97088e4d-2bea-a1c6-a01d-b1282bb0061e",
    "accessor": "69229837-03d9-adb5-5ae9-9c1b91c05858",
    "policies": [
      "default",
      "example-app-policy"
    ],
    "metadata": {
      "role_name": "example-app"
    },
    "lease_duration": 1200,
    "renewable": true,
    "entity_id": "83e197b4-42b6-b88c-b94e-debc8336e96c"
  }
}

SecretID (Push Mode)

For the Push mode, we can define a custom secret_id value and set it for a specific AppRole. In this mode, we "push" the value into Vault:

CLI

vault write /auth/approle/role/example-app/custom-secret-id secret_id=test-secret-id-1

# Output
Key               	Value
---               	-----
secret_id         	test-secret-id-1
secret_id_accessor	24afc56a-a507-7fc5-1c94-a4a3b0189d03

API

tee payload.json <<EOF
{
  "secret_id": "test-secret-id-1"
}
EOF

curl -s \
  --header "X-Vault-Token: $VAULT_TOKEN" \
  --request POST \
  --data @payload.json \
  $VAULT_ADDR/v1/auth/approle/role/example-app/custom-secret-id | jq

# Output
{
  "request_id": "66570588-3c64-2ec3-c911-03936fc0db7d",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "secret_id": "test-secret-id-1",
    "secret_id_accessor": "d3ace8d2-685a-21d9-5241-72c2ad64e0c9"
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}

We can define multiple custom secret_id values for an AppRole role and list them out:

CLI

vault write /auth/approle/role/example-app/custom-secret-id secret_id=test-secret-id-2

# Output
Key               	Value
---               	-----
secret_id         	test-secret-id-2
secret_id_accessor	bc94f717-8c14-b653-dd4a-1d106b97e327

vault list /auth/approle/role/example-app/secret-id

# Output
Keys
----
24afc56a-a507-7fc5-1c94-a4a3b0189d03
bc94f717-8c14-b653-dd4a-1d106b97e327

API

tee payload.json <<EOF
{
  "secret_id": "test-secret-id-2"
}
EOF

curl -s \
  --header "X-Vault-Token: $VAULT_TOKEN" \
  --request POST \
  --data @payload.json \
  $VAULT_ADDR/v1/auth/approle/role/example-app/custom-secret-id | jq

# Output
{
  "request_id": "6942d635-4d20-6c92-6182-caffe6a36ca8",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "secret_id": "test-secret-id-2",
    "secret_id_accessor": "721cb1c3-b66c-6cba-893e-a3ca53f4d5c7"
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}

curl -s\
  --header "X-Vault-Token: $VAULT_TOKEN" \
  --request LIST \
  $VAULT_ADDR/v1/auth/approle/role/example-app/secret-id | jq

# Output
{
  "request_id": "ff73ea79-750b-1909-e7f4-59f1bdcc695f",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "keys": [
      "d3ace8d2-685a-21d9-5241-72c2ad64e0c9",
      "721cb1c3-b66c-6cba-893e-a3ca53f4d5c7"
    ]
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}

SecretID Push Mode Use Case

The primary use case for the SecretID Push mode was to support compatibility with the deprecated App-ID backend.

However, this approach is useful in certain situations where an organization has a desire/need to pre-populate secret_id values for an AppRole. For example, they might be based on known parameters of a server or application that are hashed together or a System UUID:

CLI

vault write /auth/approle/role/example-app/custom-secret-id secret_id=$(sudo dmidecode -s system-uuid)

# Output
Key               	Value
---               	-----
secret_id         	E4B4399A-5711-432A-A7D7-6D5FBD6B3842
secret_id_accessor	f34f9c84-7d27-d3c4-45a9-10d90c3a467c

vault list /auth/approle/role/example-app/secret-id

# Output
Keys
----
f34f9c84-7d27-d3c4-45a9-10d90c3a467c

API

tee payload.json <<EOF
{
  "secret_id": "$(sudo dmidecode -s system-uuid)"
}
EOF

curl -s \
  --header "X-Vault-Token: $VAULT_TOKEN" \
  --request POST \
  --data @payload.json \
  $VAULT_ADDR/v1/auth/approle/role/example-app/custom-secret-id | jq

# Output
{
  "request_id": "0eff9d2b-bed5-515e-301a-59071c95b15d",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "secret_id": "E4B4399A-5711-432A-A7D7-6D5FBD6B3842",
    "secret_id_accessor": "093396e5-cfdd-7064-3fa4-9523a362f106"
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}

The potential benefit to this approach is that there is no longer a need to use something like Chef or some other process to deliver the secret_id to the example-app instances, as they can be generated, on-the-fly, by the instance itself when logging in to Vault:

CLI

vault write auth/approle/login \
    role_id=b0559830-1fc9-a751-e866-ff04f9dbe58e \
    secret_id=$(sudo dmidecode -s system-uuid)

# Output
Key                 	Value
---                 	-----
token               	7fb0fbe4-1c4e-de7f-10be-3a16c852c12a
token_accessor      	230061da-356b-5359-ed3f-c1f719cc2ee2
token_duration      	20m0s
token_renewable     	true
token_policies      	[default example-app-policy]
token_meta_role_name	"example-app"

API

tee payload.json <<EOF
{
  "role_id": "b0559830-1fc9-a751-e866-ff04f9dbe58e",
  "secret_id": "$(sudo dmidecode -s system-uuid)"
}
EOF

curl -s \
  --request POST \
  --data @payload.json \
  $VAULT_ADDR/v1/auth/approle/login | jq

# Output
{
  "request_id": "08a7eb72-c77b-0b7e-24a5-3bc4e6e41b00",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": null,
  "wrap_info": null,
  "warnings": null,
  "auth": {
    "client_token": "13a3c271-4bf3-99b4-747c-c31a2d493fa6",
    "accessor": "d20e4626-d0d6-c816-b2f0-bf3955ea7325",
    "policies": [
      "default",
      "example-app-policy"
    ],
    "metadata": {
      "role_name": "example-app"
    },
    "lease_duration": 1200,
    "renewable": true,
    "entity_id": "33daf519-acfa-f1f6-b826-5c647167c21e"
  }
}

For more information regarding the caveats of the Push mode of operation, refer to the documentation here: Auth Backend: AppRole - Vault by HashiCorp.

Response Wrapping the SecretID

Another level of protection that we can provide when interacting with an AppRole's secret_id is to request Vault to wrap the response with a one-time use token. We use the -wrap-ttl parameter (CLI) or X-Vault-Wrap-TTL header (API):

CLI

vault write -wrap-ttl=60s -f /auth/approle/role/example-app/secret-id

# Output
Key                          	Value
---                          	-----
wrapping_token:              	17b64c34-3162-6848-8113-858517755ab3
wrapping_token_ttl:          	1m0s
wrapping_token_creation_time:	2017-10-20 22:23:10.632900124 +0000 UTC
wrapping_token_creation_path:	auth/approle/role/example-app/secret-id

API

curl -s \
  --header "X-Vault-Token: $VAULT_TOKEN" \
  --header "X-Vault-Wrap-TTL:60s" \
  --request POST \
  $VAULT_ADDR/v1/auth/approle/role/example-app/secret-id | jq

# Output
{
  "request_id": "",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": null,
  "wrap_info": {
    "token": "ae084e63-c304-6230-9f39-d34db5527e2b",
    "ttl": 60,
    "creation_time": "2017-10-21T03:02:38.520841856Z",
    "creation_path": "auth/approle/role/example-app/secret-id"
  },
  "warnings": null,
  "auth": null
}

We can then deliver this token (via Chef, for example) to the client, which would then perform an unwrap operation to obtain the secret_id value:

CLI

vault unwrap 17b64c34-3162-6848-8113-858517755ab3

# Output
Key               	Value
---               	-----
secret_id         	321b854c-1200-ba4b-8445-80a84dd1603d
secret_id_accessor	4bbc0b58-f7bd-1b98-d81a-5868e390c5da

API

curl -s \
  --header "X-Vault-Token: ae084e63-c304-6230-9f39-d34db5527e2b" \
  --request POST \
  $VAULT_ADDR/v1/sys/wrapping/unwrap | jq

# Output
{
  "request_id": "85f52b37-a41a-6c2e-655a-54ae3313489f",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "secret_id": "f4b6ab38-ba3c-5856-dad8-7cb1c4acbd66",
    "secret_id_accessor": "171a1915-336a-5de3-1025-336bab4534c7"
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null
}

This further reduces the potential for compromise by allowing access to the generated secret_id value once, and only once.

Using the AppRole Backend With Chef

Before setting things up on the Chef side, let's create a limited token that we'll provide to Chef that'll only have permissions to retrieve secret_id values from the AppRole example-app role that we created above. First, let's create the policy that we'll attach to the token:

CLI

tee chef-approle-policy.hcl <<EOF
path "auth/approle/role/example-app/secret-id" {
    capabilities = ["update"]
}
EOF

vault write sys/policy/chef-approle-policy [email protected]

API

tee payload.json <<EOF
{
  "rules": "path \"auth/approle/role/example-app/secret-id\" { capabilities = [\"update\"] }"
}
EOF

curl \
  --header "X-Vault-Token: $VAULT_TOKEN" \
  --request POST \
  --data @payload.json \
  $VAULT_ADDR/v1/sys/policy/chef-approle-policy 

Now, let's generate a token with the above policy attached. This will typically be a longer-lived token. In the case of Chef, you might protect this token with an encrypted data bag or chef-vault, for example. Remember, this a very limited-scope token and can't be used to retrieve any secret other than the secret_id for our AppRole example:

CLI

vault token-create -policy="chef-approle-policy"

# Output
Key            	Value
---            	-----
token          	4ba2f57a-2493-eeb9-b4af-f4b280b9ab8d
token_accessor 	bc2622b0-37ad-c137-2ffc-f0a58e93106e
token_duration 	768h0m0s
token_renewable	true
token_policies 	[chef-approle-policy default]

API

tee payload.json <<EOF
{
  "policies": [
    "chef-approle-policy"
  ]
}
EOF

curl -s \
  --header "X-Vault-Token: $VAULT_TOKEN" \
  --request POST \
  --data @payload.json \
  $VAULT_ADDR/v1/auth/token/create | jq

# Output
{
  "request_id": "1715b163-5197-e94c-83ff-1434b6708c5b",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": null,
  "wrap_info": null,
  "warnings": null,
  "auth": {
    "client_token": "e2ca6c70-6309-5fb7-7f91-8ac3e08b1d12",
    "accessor": "8fdf3696-0b81-5d81-b95d-686d1d6e3e72",
    "policies": [
      "chef-approle-policy",
      "default"
    ],
    "metadata": null,
    "lease_duration": 2764800,
    "renewable": true,
    "entity_id": ""
  }
}

Now that we have our Chef AppRole token, let's create an example Chef recipe to demonstrate one way to provide an integration with Vault.

For the purpose of this demonstration, we'll set up Chef using the Quick Start — Chef Docs documentation, specifically running the chef-client in --local-mode to keep things simple:

curl -O https://packages.chef.io/files/stable/chefdk/2.3.4/ubuntu/16.04/chefdk_2.3.4-1_amd64.deb
sudo apt install ./chefdk_2.3.4-1_amd64.deb

Install the Vault Ruby client: https://github.com/hashicorp/vault-ruby

chef gem install vault

Create our example cookbook:

chef generate app chef_vault_example

This will create a directory called chef_vault_example using the standard Chef directory structure. Our recipes will be located in the chef_vault_example/cookbooks/chef_vault_example/recipes/ directory.

We'll use a custom resource (LWRP) for most of the logic needed to interact with Vault. Let's generate an empty vault.rb file:

chef generate lwrp chef_vault_example/cookbooks/chef_vault_example/ vault

We can then edit the chef_vault_example/cookbooks/chef_vault_example/resources/vault.rb file:

###
# Retrieve secrets from HashiCorp Vault
###
require 'vault'

resource_name :vault_secret
provides :vault_secret

property :path, String, name_property: true
property :destination, String
property :address, String, default: 'http://127.0.0.1:8200'

###
# Retrieve secret_id via AppRole
###
action :approle_read do
  # run_state destination defaults to path
  destination ||= new_resource.path

  # Instantiate Vault client
  vault = Vault::Client.new(address: new_resource.address)

  # Token to generate secret_id from AppRole
  #     this should be moved to encrypted databag, chef-vault, or other mechanism
  vault.token = '4ba2f57a-2493-eeb9-b4af-f4b280b9ab8d'

  # Get our secret_id login
  secret_id = vault.approle.create_secret_id('example-app').data[:secret_id]

  # We can also response wrap our secret_id
  #
  # Get response-wrapped secret_id
  # wrapped = vault.approle.create_secret_id('example-app', {
  #   wrap_ttl: "30s"
  # })
  #
  # Unwrap the wrapped response to get the secret_id and other values
  # unwrapped = vault.logical.unwrap(wrapped.wrap_info.token)
  #
  # Extract the secret_id from the response.
  # secret_id = unwrapped.data[:secret_id]
  
  # Do our AppRole login
  vault.auth.approle( ENV['ROLE_ID'], secret_id )

  # Secret retrieval
  secret = vault.logical.read(destination)

  # Retrieve data
  node.run_state[destination] = secret.data
end

Now we'll update our default recipe (chef_vault_example/cookbooks/chef_vault_example/recipes/default.rb) to make use of our new custom resource:

vault_secret '/secret/example-app' do
  notifies :create, "template[/tmp/v_test]", :immediately
end

template '/tmp/v_test' do
  source 'test.erb'
  variables lazy {
    { :secret => node.run_state['/secret/example-app'].to_s }
  }
end

In the above example, we're using the Vault Ruby client. We pass the Vault client our Chef token, which it then uses to generate to generate a secret_id against our example-app AppRole. We also have our role_id saved as an environment variable on the client system and retrieve it in our recipe. Together with both these values, we then perform an AppRole authentication and retrieve secrets from our example-app path. We then use a template resource to write out the secrets to the /tmp/v_test file.

The last step here of our setup is to create our ERB file so we can output our secrets to a text file:

chef generate template chef_vault_example/cookbooks/chef_vault_example/ test

Edit the chef_vault_example/cookbooks/chef_vault_example/templates/test.erb file:

Value retrieved from /secret/example-app: <%= @secret %>

Finally, we can run our cookbook. Change into your cookbook directory chef_vault_example/cookbooks/chef_vault_example/ and run the following:

chef-client --local-mode --override-runlist chef_vault_example

If all is successful, our output file /tmp/v_test should output the following:

Value retrieved from /secret/example-app: {:foo=>"bar"}

About

Some integration patterns using Vault AppRole authentication and Chef

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published