Vault is the latest Hashicorp open-source project.
It’s a client/server tool to securely store & access any kind of secrets like API keys, passwords, certificates etc.
There’s a seal/unseal mechanism requiring a defined amount of keys, as well as user access management & control.
Various backends are available (like AWS dynamic access keys generation), and various authentication backends are available (like GitHub).
It means for example that in a complicated situation (laptop stolen, server compromised…), a single authorized person can seal the vault so the secrets can’t be accessed.
You can find a very nice Vault Interactive Tutorial here.
Those notes are mostly from the 9-parts-long Getting Started with Vault.
Installation
Vault is at its first version at the time of this writing: 0.1.0 – download it now.
Put it somewhere like /opt/vault
:
$ sudo mkdir -p "/opt/vault" && cd "$_" && sudo unzip ~/Downloads/vault*.zip
Adapt your $PATH:
$ PATH=$PATH:/opt/vault
Does it work ?
$ vault version
Vault v0.1.0-dev (e9b3ad035308f73889dca383c8c423bb5939c4fc+CHANGES)
Yes it does.
Start a Vault Server
We’ll launch a dev Vault server. A dev server is 100% in memory, with no backed, and have a single unseal key.
$ vault server -dev
WARNING: Dev mode is enabled!
In this mode, Vault is completely in-memory and unsealed.
Vault is configured to only have a single unseal key. The root
token has already been authenticated with the CLI, so you can
immediately begin using the Vault CLI.
The only step you need to take is to set the following
environment variable since Vault will be taking without TLS:
export VAULT_ADDR='http://127.0.0.1:8200'
The unseal key and root token are reproduced below in case you
want to seal/unseal the Vault or play with authentication.
Unseal Key: 6a72f9d9448bfe1a2df3c0d5665c7c0cfc8071c9b5cc9ffe58c8a1e4816814b1
Root Token: 8c40f7b4-1380-8a33-8d3b-5efd2dddfe51
==> Vault server configuration:
Log Level: info
Backend: inmem
Listener 1: tcp (addr: "127.0.0.1:8200", tls: "disabled")
==> Vault server started! Log data will stream in below:
2015/04/30 16:33:30 [INFO] core: security barrier initialized
2015/04/30 16:33:30 [INFO] core: post-unseal setup starting
2015/04/30 16:33:30 [INFO] core: post-unseal setup complete
2015/04/30 16:33:30 [INFO] core: root token generated
2015/04/30 16:33:30 [INFO] core: pre-seal teardown starting
2015/04/30 16:33:30 [INFO] rollback: starting rollback manager
2015/04/30 16:33:30 [INFO] rollback: stopping rollback manager
2015/04/30 16:33:30 [INFO] core: pre-seal teardown complete
2015/04/30 16:33:30 [INFO] core: vault is unsealed
2015/04/30 16:33:30 [INFO] core: post-unseal setup starting
2015/04/30 16:33:30 [INFO] core: post-unseal setup complete
2015/04/30 16:33:30 [INFO] rollback: starting rollback manager
As said it the output:
$ export VAULT_ADDR='http://127.0.0.1:8200'
And don’t forget to temporarily store the Unseal Key and the Root Token somewhere.
Verify the server is running:
$ vault status
Sealed: false
Key Shares: 1
Key Threshold: 1
Unseal Progress: 0
High-Availability Enabled: false
This basically means that the vault is unsealed, with 1 key and a threshold of 1 to manipulate the vault.
Manipulate secret entries
Our dev server stores everything in memory without the need of a backend. In production, the backend might be on-disk or Consul.
Let’s store John Doe’s password under the secret/
path prefix:
$ vault write secret/johndoe value=SuperSecretPassword
Success! Data written to: secret/johndoe
Let’s store his secret token as well:
$ vault write secret/johndoe value=SuperSecretPassword token=ABCDE12345
Success! Data written to: secret/johndoe
Let’s read that information now:
$ vault read secret/johndoe
Key Value
lease_id secret/johndoe/8fe1128a-c25a-6a05-4409-ed12e4ac7b6f
lease_duration 2592000
token ABCDE12345
value SuperSecretPassword
or if you prefer a JSON output:
$ vault read --format=json secret/johndoe
{
"lease_id": "secret/johndoe/1efe25c0-f4cc-2e66-c17a-dd901c443f19",
"lease_duration": 2592000,
"renewable": false,
"data": {
"token": "ABCDE12345",
"value": "SuperSecretPassword"
}
}
Let’s now delete our entry “token” from johndoe:
$ vault delete secret/johndoe/token
Success! Deleted 'secret/johndoe/token'
Or the whole thing:
$ vault delete secret/johndoe
Success! Deleted 'secret/johndoe'
Backends
We just used a generic backend: secret/
. There’s others, like AWS.
Backends can be mounted, like a regular filesystem.
Let’s mount a generic backend:
$ vault mount generic
Successfully mounted 'generic' at 'generic'!
Inspect the mounts:
$ vault mounts
Path Type Description
generic/ generic
secret/ generic generic secret storage
sys/ system system endpoints used for control, policy and debugging
Let’s write an entry under generic/
:
$ vault write generic/janedoe value=BetterSecretPassword
Success! Data written to: generic/janedoe
and read it:
$ vault read generic/janedoe
Key Value
lease_id generic/janedoe/b857ebfc-e6f5-6e19-a851-74d04494cf43
lease_duration 2592000
value BetterSecretPassword
You can unmount the generic backend:
$ vault unmount generic/
Successfully unmounted 'generic/'!
This will erase all data from the backend. If you mount it again, it will be empty.
Dynamic Secrets
Dynamic Secrets are like regular secrets, except they don’t exist until they are accessed.
Here we’ll generate and revoke on-the-fly AWS IAM credentials. You just need your AWS access keys.
Mount the AWS backend:
$ vault mount aws
The AWS backend is mounted at aws/
.
Let’s configure it by writing the root credentials values under it:
$ vault write aws/config/root
access_key=ABCDE1234
secret_key=1234ABCDE
Success! Data written to: aws/config/root
AWS IAM needs a role policy to work with, here’s one simple JSON policy file, that does allow to do everything inside EC2. Save it and name it policy.json
.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1426528957000",
"Effect": "Allow",
"Action": [
"ec2:*"
],
"Resource": [
"*"
]
}
]
}
We’ll now create a new role: deploy with that policy file:
$ vault write aws/roles/deploy policy=@policy.json
Success! Data written to: aws/roles/deploy
Note that we used the syntax @filename
to load a file and not a value.
Now we want to generate a dynamic secret access keypair. Just read the role name under aws/creds
:
$ vault read aws/creds/deploy
Key Value
lease_id aws/creds/deploy/185e6910-6d36-e9a6-33b3-fc8dcfd4e97c
lease_duration 3600
access_key ABCDE9876
secret_key qwerty!@#$
Those keys are now real AWS keys. If you look under your IAM/Users page on the AWS Console, you’ll see it.
You’ll create new additional keys every time you read the entry.
For now, grab the lease_id
, it will be useful to revoke the access.
To revoke the key:
$ vault revoke aws/creds/deploy/185e6910-6d36-e9a6-33b3-fc8dcfd4e97c
Key revoked with ID 'aws/creds/deploy/185e6910-6d36-e9a6-33b3-fc8dcfd4e97c'.
Check on the AWS console, they aren’t there anymore.
Vault Authentication
Tokens to authenticate always have a parent. It’s useful to delete a whole tree of credentials to be revoked at once.
You can create more child tokens with:
$ vault token-create
e0760b10-9cdb-ec78-39e2-480b4b806d55
Or revoke it later:
$ vault token-revoke
e0760b10-9cdb-ec78-39e2-480b4b806d55
Or login with it:
$ vault auth 8c40f7b4-1380-8a33-8d3b-5efd2dddfe51
Successfully authenticated! The policies that are associated
with this token are listed below:
root
Keep the original root token when playing with authentication and revokations.
Let’s create an authentication system with GitHub:
$ vault auth-enable github
Successfully enabled 'github' at 'github'!
Now, GitHub authentication backend is mounted under auth/
.
Let’s say your GitHub organisation is CrazyInc, configure the auth to allow only users from this org:
$ vault write auth/github/config organization=CrazyInc
Success! Data written to: auth/github/config
And with the same role than ours (root):
$ vault write auth/github/map/teams/default value=root
Success! Data written to: auth/github/map/teams/default
Get your user’s GitHub personal access token and authenticate. If you’re not part of the above org, you’ll won’t be accepted:
$ vault auth -method=github token=zxc123
Error making API request.
URL: PUT http://127.0.0.1:8200/v1/auth/github/login
Code: 400. Errors:
* user is not part of required org
But if the user is, authentication will work:
$ vault auth -method=github token=asdqwe456789
Successfully authenticated! The policies that are associated
with this token are listed below:
root
If you want to revoke all GitHub tokens:
$ vault token-revoke -mode=path auth/github
You’ll now probably need to re-auth with a root token.
And you can now even disable fully all GitHub authentication:
$ vault auth-disable github
ACL
You can list available policies, as root:
$ vault policies
root
Only one is available at first.
Here’s an example ACL policy, save it to acl.hcl
:
path "sys" {
policy = "deny"
}
path "secret" {
policy = "write"
}
path "secret/foo" {
policy = "read"
}
It will deny access to sys/
, default write access to secret/
except for secret/foo
where read-only is applied.
Vault defaults to deny when not specified.
Apply it:
$ vault policy-write secret acl.hcl
Policy 'secret' written.
You can create another one just to delete it:
$ vault policy-write secret123 acl.hcl
Policy 'secret123' written.
$ vault policies
secret
secret123
root
Delete it:
$ vault policy-delete secret123
Policy 'secret123' deleted.
Create a new token using this policy:
$ vault token-create -policy="secret"
3c183cf2-822a-29de-ece2-393f9d964655
Authenticate with it:
$ vault auth 3c183cf2-822a-29de-ece2-393f9d964655
Successfully authenticated! The policies that are associated
with this token are listed below:
secret
See, no more root access, but secret level.
Try to write to secret/
and confirm you can only read from secret/foo
as per the ACL:
$ vault write secret/bar value=yes
Success! Data written to: secret/bar
$ vault write secret/foo value=yes
Error writing data to secret/foo: Error making API request.
URL: PUT http://127.0.0.1:8200/v1/secret/foo
Code: 400. Errors:
* permission denied
Not having access to sys/
also prevents to use mounts:
$ vault mount generic
Mount error: Error making API request.
URL: POST http://127.0.0.1:8200/v1/sys/mounts/generic
Code: 500. Errors:
* permission denied