/ vault

Vault AppRole Authentication

Earlier, in a few blog entries starting here, I installed and configured HashiCorp Vault on my laptop. It was pretty fun and easy. The reason I did this was to learn more about the product, and how to go about using it in some real-world-type scenarios. This post is a furthering of that learning. I want to learn what the AppRole authentication method is, and how to use it. Specifically, in my case, how to go about using it with the product Chef.

What is AppRole anyway

Vault does what it does by utilizing different "backends". For authentication, it uses the auth backend. Surprising, right? Well, anyway, in the previous posts I used the token auth backend to authenticate to Vault on my laptop. For instance:

vault auth 210cd6ff-26f1-49e6-940e-3f7dd5ae0671
Successfully authenticated! You are now logged in.
token: 210cd6ff-26f1-49e6-940e-3f7dd5ae0671
token_duration: 2764773
token_policies: [chef-ro default]

With this auth backend I've provided a token (a thing that I know) in order to tell Vault who I am. Based on the policies set in the definition of that token, I am allowed to do things. In the output above, you can see that I've been given the policies called chef-ro and default. I have previously created chef-ro for my own fun, and created this token to test it (more on that in a bit). Until I auth with another token, or this token times out, I can do the things those policies say I can. Meaning I can list, read, update, etc whatever is defined within them. If I wanted to write scripts around the things that are allowed to that token, I could store it by some secure means, and call it with the scripts or programs, and use it to auth to Vault automatically. Thinking about this, I can see where that starts to become problematic. I can imagine something I'd call token sprawl. Eventually there could be hundreds or thousands of tokens defined in Vault for certain processes, or to allow certain machines to do things. Depending on the circumstances, that certainly makes sense. However, if I want to utilize the secrets in the Vault with an automation platform such as Chef, I would want some kind of an automated process around getting these tokens. With a product like Chef, I may not even want the tokens I use to have a lifespan past what is required to retrieve the secrets I need during the run.

Why would I use this in conjunction with Chef (or Ansible, Puppet, SaltStack, etc)? In any kind of scripting or programming, there are always things that need to be abstracted out of the code in order to make it more usable, commonly known as variables. With Chef these are called attributes. Sometimes these attributes may be of a sensitive nature, and so should be protected in some way. Chef can do this within it's own technology stack in a couple different ways: encrypted databags and Chef vault. Both of them have their up and downsides, but that's not in scope here. Maybe the things being stored aren't even that confidential, but a few different applications may want to have that same information. Perhaps there is a mix of automation technologies within an architecture, and having the details of that architecture stored centrally requires less processes around updating that information set. If I'm going to centrally store any kind of architectural information, I'd want it encrypted and secured by default.

So, if I'm going to use Vault for retrieving attributes to use within Chef recipes, then I don't really want to create a token for every machine that will be running my configuration code, for a few reasons.

  • Server provisioning becomes more complicated if a new token needs to be created for every new instance at build time. The systems provisioning servers could be separate, and so may require different process to get the token. That feels kind of like re-inventing the wheel a lot. If I already have a central thing to do configuration, why not just use that?
  • If I'm decommissioning servers, then I'd want to revoke the tokens. That adds a step just like in the first point.
  • In order to maintain some security around the tokens, I need to be able to rotate them. That is another thing I have to make sure is secure. I'm just moving the security holes up the stack.
  • To create alerts around the token, for warnings about possible tokens having been leaked into the wild, there needs to be a mapping of token to server. That seems a bit exhausting. For instance, if someone hacks into my server459.test.com and gains access to my token, I'd have to know that specific token belongs only to server459.test.com. Another thing to keep up with for proper alerting.

To be fair, I could be wrong about some or all of those assertions. But they made me want to learn about the AppRole auth backend that Vault has provided. I mean, their documentation states that it is specifically for scenarios like I'm describing:

This backend allows machines and services (apps) to authenticate with Vault via a series of administratively defined roles: AppRoles. The open design of AppRole enables a varied set of workflows and configurations to handle large numbers of apps and their needs. This backend is oriented to automated workflows, and is the successor to the App-ID backend.

As far as I understand it, retrieving something from Vault via the AppRole method is as follows:

  1. With a token given the proper permissions, create a new secret-id on the role. Said secret-id can have constraints on number and time frame of use(s)
  2. Given the correct role-id and newly created secret-id, login to the role
  3. The response to #3 will be a token which can have constraints on number and time frame of use(s)
  4. The token from #4 is used to auth to Vault (hint: this is the AHA! moment)
  5. Use the newly auth'd session to retrieve the secrets

Ok, so I can create dynamic secrets to retrieve dynamic tokens. Perfect. Of course you can also just retrieve a token and store it for future use if it is configured properly, but that's not what I'm getting at with my project.

I'm going to need a few things to test this out:

  1. Secret(s) in the Vault
  2. Policy around reading the secret(s)
  3. AppRole authentication enabled
  4. A role within the backend that relates to #2 above
  5. A token that can start the process of getting the necessary credentials for logging in

So, lets get started.

SHH! It's a secret

I'm going to create a new kv (previously known as generic) backend for the new secret:

vault mount -path=chef-secret kv
Successfully mounted 'kv' at 'chef-mount'!
vault mounts
Path          Type       Accessor            Plugin  Default TTL  Max TTL  Force No Cache  Replication Behavior  Description
chef-secret/  kv         kv_10ca46dd         n/a     system       system   false           replicated
cubbyhole/    cubbyhole  cubbyhole_6222f487  n/a     n/a          n/a      false           local                 per-token private secret storage
identity/     identity   identity_902aecf8   n/a     n/a          n/a      false           replicated            identity store
secret/       kv         kv_aee6d2a6         n/a     system       system   false           replicated            key/value secret storage
sys/          system     system_042cc31d     n/a     n/a          n/a      false           replicated            system endpoints used for control, policy and debugging

There it is. Next up, put a secret in there:

vault write chef-secret/test-secret -value='test secret value'
Success! Data written to: chef-secret/test-secret
vault list chef-secret
Keys
- - - -
test-secret

vault read chef-secret/test-secret
Key                     Value
- - -                   - - - - -
refresh_interval        768h0m0s
-value                  test secret value

In order to make the retrieval of the secrets a little more secure for the automated processes, create a read-only policy around them:

cat chef-ro.hcl
path "chef-secret/*" {
   capabilities = ["read", "list"]
}

vault policy-write chef-ro chef-ro.hcl
Policy 'chef-ro' written.

vault policies
chef-ro
default
root

vault read /sys/policy/chef-ro
Key     Value
- - -    - - - - -
name    chef-ro
rules   path "chef-secret/*" {
   capabilities = ["read", "list"]
}

Great, now lets test that policy with a new token that has that policy assigned:

vault token-create -policy="chef-ro"
Key             Value
- - -            - - - - -
token           210cd6ff-26f1-49e6-940e-3f7dd5ae0671
token_accessor  8b20812d-62ef-6cc0-7132-937e73dd8ae8
token_duration  768h0m0s
token_renewable true
token_policies  [chef-ro default]

vault auth 210cd6ff-26f1-49e6-940e-3f7dd5ae0671
Successfully authenticated! You are now logged in.
token: 210cd6ff-26f1-49e6-940e-3f7dd5ae0671
token_duration: 2764773
token_policies: [chef-ro default]

vault list chef-secret
Keys
- - - -
test-secret

vault read chef-secret/test-secret
Key                     Value
- - -                   - - - - -
refresh_interval        768h0m0s
-value                  test secret value

This policy is now a re-usable thing that I can apply to an AppRole in order to facilitate my automation.

Configuring AppRole

The first thing we have to do to start using the AppRole authentication backend is to enable it, like so:

vault auth-enable approle
Successfully enabled 'approle' at 'approle'!

Now to create thef role that I'm going to use for my Chef project:

vault write auth/approle/role/chef-ro secret_id_ttl=1m secret_id_num_uses=1 token_num_uses=3 token_ttl=10m token_max_ttl=30m policies=chef-ro
Success! Data written to: auth/approle/role/chef-ro
vault read auth/approle/role/chef-ro
Key                     Value
- - -                   - - - - -
bind_secret_id          true
bound_cidr_list
period                  0
policies                [chef-ro]
secret_id_num_uses      1
secret_id_ttl           60
token_max_ttl           1800
token_num_uses          3
token_ttl               600

I defined the role by writing it to the AppRole authentication backend. I defined it to:

  • have the human readable name of 'chef-ro'
  • only allow the secret_id that will be created to be valid for 1 minute
  • only allow the secret_id to be used once, so all new authentications will need thier own secret_id
  • the token that will be given once the process authenticates can only be used 3 times
  • the token's ttl will be 10 minutes, with a 30 minute maximum lifespan
  • the token will have the policy 'chef-ro', which was created in the previous steps

In order to test this role, I need to know it's internal id:

vault read auth/approle/role/chef-ro/role-id
Key     Value
- - -   - - - - -
role_id 8fe67d14-c109-c7c6-3997-576919541141

Ok, now I have the necessary information. I'm already authenticated with a token that has all rights, so I can just go ahead and test the login process. First step is to generate that secret_id. That's done by writing to the correct path, which will return a randomly generated secret_id for use in the AppRole login process (where we get an actual token):

vault write -f auth/approle/role/chef-ro/secret-id
Key                     Value
- - -                   - - - - -
secret_id               14ba4ebb-b832-f69b-1036-0a92a7129c76
secret_id_accessor      ef89fcf8-da6e-a1dc-c6e1-9818958a50f1

Ok, stay with me here. At first it seems kinda weird, but it starts to make sense once you see it in action.

Now that I have the role_id and a fresh secret_id, I can log into the role by writing to the AppRole login path. This will give me a token with the configuration I defined in the role creation:

vault write auth/approle/login role_id=8fe67d14-c109-c7c6-3997-576919541141 secret_id=14ba4ebb-b832-f69b-1036-0a92a7129c76
Key                     Value
- - -                   - - - - -
token                   e2a9855d-bfe4-a289-29f9-a3fee4fb0d34
token_accessor          70635d83-2b1d-b9dc-b675-612080e8ac5a
token_duration          10m0s
token_renewable         true
token_policies          [chef-ro default]
token_meta_role_name    "chef-ro"

So there we have a token that can read our Chef secrets, because it has the "chef-ro" policy that we tested before. Lets test this new token now:

vault auth e2a9855d-bfe4-a289-29f9-a3fee4fb0d34
Successfully authenticated! You are now logged in.
token: e2a9855d-bfe4-a289-29f9-a3fee4fb0d34
token_duration: 588
token_policies: [chef-ro default]
vault list chef-secret
Keys
- - - -
test-secret

vault read chef-secret/test-secret
Key                     Value
- - -                   - - - - -
refresh_interval        768h0m0s
-value                  test secret valu

There, I've gone through the AppRole authentication process and retrieved a secret! Simple yeah? Ok, I wouldn't want to do that manually all the time, but it is perfect for automation. When it has become code, it will be easily re-usable.

One last step

In the section above, I generated the secret_id by writing to the secret_id path of the role. I was able to do that because I was using the root token still. Since the root token has all the rights, it's a extremely terrible idea to distribute it to the automation stuff. In real life, it should be deleted immediately after it is used to create other tokens with authority. So I'll create a token that is designed to just generate the secret_id for the AppRole(s) I'll be using. This token cannot read the Chef secrets, so it is safer to distribute. It can be secured in whatever manner makes sense for the product or process that will be utilizing it.

cat ar-token-create.hcl
# chef-ro approle
path "auth/approle/role/chef-ro/secret-id" {
    capabilities = ["update"]
}

vault write sys/policy/ar-token-create policy=@ar-token-create.hcl
Success! Data written to: sys/policy/ar-token-create
vault token-create -policy=ar-token-create
Key             Value
- - -           - - - - -
token           d39be0b3-8fcc-1b01-ed65-2267504057ea
token_accessor  e2696eb0-1746-2167-36a3-f2b9ba4113de
token_duration  768h0m0s
token_renewable true
token_policies  [ar-token-create default]

Now, with this new token that can only generate the secret_id, lets test the whole process. To review:

  1. Authenticate to vault with the secret_id generating token
  2. Get a secret_id for the role
  3. Use the unique identifier of the role, and the newly created secret_id to log into the role, resulting in a token
  4. Use the token generated in step #3, and authenticate to Vault
  5. List and/or read the secret(s) we are after
vault auth d39be0b3-8fcc-1b01-ed65-2267504057ea
Successfully authenticated! You are now logged in.
token: d39be0b3-8fcc-1b01-ed65-2267504057ea
token_duration: 2761115
token_policies: [ar-token-create default]

vault write -f auth/approle/role/chef-ro/secret-id
Key                     Value
- - -                   - - - - -
secret_id               1126cb4b-b923-6406-a921-8ed361073767
secret_id_accessor      0ae809a3-f57d-9199-573c-b56173801cb5

vault write auth/approle/login role_id=8fe67d14-c109-c7c6-3997-576919541141 secret_id=1126cb4b-b923-6406-a921-8ed361073767
Key                     Value
- - -                   - - - - -
token                   8664b164-efc7-ac65-7786-95eaa51f2106
token_accessor          d8e9efdc-e4d4-074f-36be-c02cc07b1227
token_duration          10m0s
token_renewable         true
token_policies          [chef-ro default]
token_meta_role_name    "chef-ro"

vault auth 8664b164-efc7-ac65-7786-95eaa51f2106
Successfully authenticated! You are now logged in.
token: 8664b164-efc7-ac65-7786-95eaa51f2106
token_duration: 591
token_policies: [chef-ro default]

vault read chef-secret/test-secret
Key                     Value
- - -                    - - - - -
refresh_interval        768h0m0s
-value                  test secret value

And that is that.

Now I can move on to putting this process into code for use with Chef, but I'll do that another night.