libvault v0.1.0 Vault
The main module for configuring and interacting with HashiCorp’s Vault.
When possible, it tries to emulate the CLI, with read
, write
, list
and
delete
methods. An additional request
method is provided when you need
further flexibility.
API Preview
vault =
Vault.new([
engine: Vault.Engine.KVV2,
auth: Vault.Auth.UserPass
])
|> Vault.auth(%{username: "username", password: "password"})
{:ok, db_pass} = Vault.read(vault, "secret/path/to/password")
{:ok, %{"version" => 1 }} = Vault.write(vault, "secret/path/to/creds", %{secret: "secrets!"})
Flexibility
Hashicorp’s Vault is highly configurable. Rather than cover every possible option, this library strives to be flexible and adaptable. Auth backends, Secret Engines, and Http clients, and JSON parsers are all replacable, and each behaviour asks for a minimal behaviour contract.
HTTP Adapters
The following HTTP Adapters are provided:
Tesla
withVault.HTTP.Tesla
- Can be configured to use
:hackney
,:ibrowse
, or:httpc
(and in turn, plays nice withHTTPoison
, andHTTPotion
)
- Can be configured to use
JSON Adapters
Most JSON libraries provide the same methods, so no default adapter is needed.
You can use Jason
, JSX
, Poison
, or whatever encoder you want.
Defaults to Jason
if present, falling back to Poison
if present,
ultimately falling back to nil
.
See Vault.JSON.Adapter
for the full behaviour interface.
Auth Adapters
Adapters have been provided for the following auth backends:
- AppRole with
Vault.Auth.Approle
- Azure with
Vault.Auth.Azure
- GitHub with
Vault.Auth.Github
- GoogleCloud with with
Vault.Auth.GoogleCloud
- JWT with
Vault.Auth.JWT
- Kubernetes with
Vault.Auth.Kubernetes
- LDAP with
Vault.Auth.LDAP
- UserPass with
Vault.Auth.UserPass
- Token with
Vault.Auth.Token
In addition to the above, a generic backend is also provided (Vault.Auth.Generic
).
If support for auth provider is missing, you can still get up and running
quickly, without writing a new adapter.
Secret Engines
Most of Vault’s Secret Engines use a replacable API. The Vault.Engine.Generic
adapter should handle most use cases for secret fetching. This is also the default value.
Vault’s KV version 2 broke away from the standard REST convention. So KV has been given its own adapter:
-
- v1 with
Vault.Engine.KVV1
- v2 with
Vault.Engine.KVV1
- v1 with
Additional request methods
The core library only handles the basics around secret fetching. If you need to
access additional API endpoints, this library also provides a Vault.request
method. This should allow you to tap into the complete vault REST API, while still
benefiting from token control, JSON parsing, and other HTTP client nicities.
Setup and Usage
First, ensure that adapter dependancies are part of your dependancies and applications:
def application do
[
extra_applications: [:logger, :tesla, :hackney,] # or :ibrowse
mod: {MyApp.Application, []}
]
end
def deps do
[
# tesla, required for Vault.HTTP.Tesla
{:tesla, "~> 1.0.0", },
# pick your HTTP client - ibrowse, hackney, or if you're feeling bold, httpc.
{:ibrowse, "~> 4.4.0", },
{:hackney, "~> 1.6", },
# Pick your json parser
{:jason, ">= 1.0.0", },
{:poison, "~> 3.0", },
]
end
Example usage:
vault =
Vault.new([
engine: Vault.Engine.KVV2,
auth: Vault.Auth.UserPass,
credentials: %{username: "username", password: "password"}
])
|> Vault.auth()
{:ok, db_pass} = Vault.read(vault, "secret/path/to/password")
{:ok, aws_creds} = Vault.read(vault, "secret/path/to/creds")
You can configure the client up front, or change configuration dynamically.
vault =
Vault.new() # use defaults, or application configuration
|> Vault.set_auth(Vault.Auth.Approle)
|> Vault.auth(%{role_id: "role_id", secret_id: "secret_id"})
{:ok, db_pass} = Vault.read(vault, "secret/path/to/password")
vault = Vault.set_engine(Vault.Engine.KVV2) // switch to versioned secrets
{:ok, db_pass} = Vault.write(vault, "kv/path/to/password", %{ password: "db_pass" })
Link to this section Summary
Functions
Authenticate against the configured auth backend
Delete a secret from the configured secret engine
List secret keys available at a certain path
Create a new client. Optionally provide a keyword list or map of options for configuration
Read a secret from the configured secret engine
Make an HTTP request against your vault instance, with the current vault token
Set the backend to use for authenticating the client
Set the path used when logging in with your auth adapter
Sets the login credentials for this client
Set the secret engine for the client
Set the host of your vault instance
Set the http module used to make API calls
Check if the current token is still valid
Get a NaiveDateTime
struct, in UTC, for when the current token will expire
Write a secret to the configured secret engine
Link to this section Types
t() :: %Vault{ auth: auth(), auth_path: auth_path(), credentials: credentials(), engine: engine(), host: host(), http: http(), json: json(), token: token(), token_expires_at: token_expires_at() }
Link to this section Functions
Authenticate against the configured auth backend.
Examples
A successful authentication returns a client containing a valid token, as well as the expiration time for the token. Perform this operation before reading or writing secrets.
Errors from vault are returned as a list of strings.
Uses pre-configured credentials if provided. Passed in credentials will override existing credentials.
{:ok, vault} = Vault.set_credentials(vault, %{username: "UserN4me", password: "P@55w0rd"})
{:error, ["Missing Credentials, username and password are required"]} = Vault.set_credentials(vault, %{username: "whoops"})
Delete a secret from the configured secret engine.
Examples
Returns the response from vault, which is typically an empty map. See Secret Engine Adapter options for further configuration.
{:ok, %{} }} = Vault.delete(vault,"secret/path/to/write")
{:error, ["Key not found"]} = Vault.list(vault,"secret/bad/path/")
List secret keys available at a certain path.
Examples
Path should end with a trailing slash. Provided adapters returns the values on
the data
key from vault, if present. See Secret Engine adapter details for
additional configuration, such as returning the full response.
Errors from vault are returned as a list of strings.
{:ok, %{ "keys" => ["some/", "paths", "returned"] }} = Vault.list(vault,"secret/path/to/write")
{:error, ["Unauthorized"]} = Vault.list(vault,"secret/bad/path/")
Create a new client. Optionally provide a keyword list or map of options for configuration.
Examples
Return a default Vault client:
vault = Vault.new()
Return a fully initialized Vault Client:
vault = Vault.new(%{
http: Vault.HTTP.Tesla,
host: myvault.instance.com,
auth: Vault.Auth.JWT,
auth_path: 'jwt',
engine: Vault.Engine.Generic,
token: "abc123",
token_expires_at: NaiveDateTime.utc_now() |> NaiveDateTime.add(30, :seconds),
credentials: %{role_id: "dev-role", jwt: "averylongstringoflettersandnumbers..."}
})
Options
The following options can be provided as part of the :vault
application config, or
as a Keyword List or Map of options. Runtime configuration will always take
precedence.
:auth
- Module for your Auth adapter.:auth_path
- Path to use for your auth adapter. Provided adapters have their own default paths. Check your adapter for details.:engine
Module for your Secret Engine adapter. Defaults toVault.Engine.Generic
.:host
- host of your vault instance. Should contain the port, if needed. Should not contain a trailing slash. Defaults toSystem.get_env("VAULT_ADDR")
.:http
- Module for your http adapter. Defaults toVault.HTTP.Tesla
when:tesla
is present.:token
- A vault token.:token_expires_at
ANaiveDateTime
instance that represents when the token expires, in utc.:credentials
- The credentials to use when authenticating with your Auth adapter.
Read a secret from the configured secret engine.
Examples
Provided adapters return the values on the data
key from vault, if present.
See Secret Engine adapter details for additional configuration, such as
returning the full response.
Errors from vault are returned as a list of strings.
{:ok, %{ password: "value" }} = Vault.write(vault,"secret/path/to/read")
{:error, ["Unauthorized"]} = Vault.read(vault,"secret/bad/path")
Make an HTTP request against your vault instance, with the current vault token.
Examples
This library doesn’t cover every vault API, but this can help fill some of the gaps, and removing some boilerplate around token management, and JSON parsing.
It can also be handy for renewing dynamic secrets, if you’re using the AWS Secret backend.
Requests can take the following options a Keyword List.
options:
:query_params
- a keyword list of query params for the request. Do not include query params on the path.:body
- Map. The body for the request:headers
- Keyword list. The headers for the request:version
- String. The vault api version - defaults to “v1”
General Example
Here’s a genneric example for making a request:
vault = Vault.new(
http: Vault.HTTP.Tesla,
host: "http://localhost",
token: "token"
token_expires_in: NaiveDateTime.utc_now()
)
Vault.request(vault, :post, "path/to/call", [ body: %{ "foo" => "bar"}])
# POST to http://localhost/v1/path/to/call
# with headers: {"X-Vault-Token", "token"}
# and a JSON payload of: "{ 'foo': 'bar'}"
AWS lease renewal
A quick example of renewing a lease.
vault = Vault.new(
http: Vault.HTTP.Tesla,
host: "http://localhost",
token: "token"
token_expires_in: NaiveDateTime.utc_now()
)
body = %{lease_id: lease, increment: increment}
{:ok, response} = Vault.request(vault, request(:put, "sys/leases/renew", [body: body])
Set the backend to use for authenticating the client.
Examples
The auth backend should be a module that meets the Vault.Auth.Adapter
behaviour.
vault = Vault.set_auth(vault, Vault.Auth.Approle)
Set the path used when logging in with your auth adapter.
Examples
Auth backends can be mounted at any path on /auth/
. If left unset, the auth adapter may
provide a default, eg userpass
. See your Auth adapter for details.
vault = Vault.set_auth_path(vault, "auth-path")
Sets the login credentials for this client.
Examples
vault = Vault.set_credentials(vault, %{username: "UserN4me", password: "P@55w0rd"})
Set the secret engine for the client.
Examples
The secret engine should be a module that meets the Vault.Engine.Adapter
behaviour.
vault = Vault.set_engine(vault, Vault.Engine.KVV2)
Set the host of your vault instance.
Examples
The host can be fetched from anywhere, as long as it’s a string.
vault = Vault.set_host(vault, System.get_env("VAULT_ADDR"))
The port should be provided if needed, along with the protocol.
vault = Vault.set_host(vault, "https://my-vault.host.com:12345")
Set the http module used to make API calls.
Examples
Should be a module that meets the Vault.HTTP.Adapter
behaviour.
vault = Vault.set_http(vault, Vault.HTTP.Tesla)
Check if the current token is still valid.
Examples
Returns true if the current time is later than the expiration date, otherwise false.
true = Vault.token_expired?(vault)
Get a NaiveDateTime
struct, in UTC, for when the current token will expire.
Examples
Expiration time is generated from the current time on the current server.
~N[2018-11-25 16:30:30.177731] = Vault.token_expires_at(vault)
Write a secret to the configured secret engine.
Examples
Provided adapters returns the values on the data
key from vault, if present.
See Secret Engine adapter details for additional configuration, such as
returning the full response.
Errors from vault are returned as a list of strings.
{:ok, %{ version: 1 }} = Vault.write(vault,"secret/path/to/write", %{ secret: "value"})
{:error, ["Unauthorized"]} = Vault.write(vault,"secret/bad/path", %{ secret: "value"})