I the previous post we talked about the basics of Vault, its architectural concepts, nomenclature and basic operations that can be performed. Now it's time to turn that theory into practice and write some code in Go that will allow us to access our secrets.

The idea

My first idea of some practical use of Vault was to build a simple command tool that will act as a handy client to the API exposed by Vault server instance. I wanted it to be easy to use and fast, to that, I wouldn't have to switch back and forth between windows in order to eg. get credentials to the MySQL instance I need to access.

Accessing Vault with Go is very easy because since the tool is written in Go, it comes with a client that is ready to use. All you need to do is import github.com/hashicorp/vault/api in your application and you can start using Vault from within your own code.

Connecting & authenticating

The first step is creating a client and authenticating to the Vault instance in order to be able to perform any operations. We do that by using api.newClient(..) function...

client, err := api.NewClient(&api.Config{
    Address: vaultAddress,
})
...

then we need to provide the access token using

...
client.SetToken(token)
...

We could hardcode the address and token in the code, or pass them as execution arguments, but in my case, I wanted to have them defined in YAML configuration file:

# ~/walter/.auth.yaml
token: MY-TOKEN
vault_addr: http://127.0.0.1:8222

From this point it's easy to read and parse the file into Go application:

type authConfig struct {
    Token     string `yaml:"token"`
    VaultAddr string `yaml:"vault_addr"`
}
...
bs, err := ioutil.ReadFile(configPath)
...
var cfg authConfig
if err := yaml.Unmarshal(bs, &cfg); err != nil {
    ...
}
...

As you can see, that didn't require a lot of effort from us, and the value doesn't have to be provided as a parameter (therefore could be found when browsing through the bash history for example). One thing that you should note is that the token for such an application should not be the root token (the one generated when Vault is initialized) because this way you would not be able to disable the access in case someone steals the token and you are afraid of them accessing your data somehow. If you create a child token, you can block it at any given time.

Reading data

Performing the most basic operations on Vault requires you to understand how the data is passed around in the client. As you could recall, whenever you wanted to read or write data in your Vault instance, you were operating on the structures that could contain one or more key-value pairs. Because of that, the data structure that is used in the Go client is a string -> interface{} map, which allows you to define arbitrary key names and various data types as values. In order to read the data, you need to use a logical backend of the API, which exposes appropriate function:

secretValues, err := c.Logical().Read(keyName)
if err != nil {
    ...
}
fmt.Printf("secret %s -> %v", keyName, secretValues)

In my case, I want to do one of two things: read the whole secret values map, or pick just one attribute and print it without the rest. Since we are operating on the map, it is very easy to do, I can just take that one value:

...
if readWhole {
    for propName, propValue := range secretValues.Data {
        fmt.Printf(" - %s -> %v\n", propName, propValue)
    }
} else {
    fmt.Printf("%s:%s -> %v\n", keyName, propertyName, secretValues.Data[propertyName])
}
...

Writing data

My concept of the application acting just as a command-line client of the API, I decided to skip the writing part of the process, limiting it to reading data only. This could act as another layer of security, in the case has a more powerful access token, so that they won't be able to alter any of the data stored in Vault. Nevertheless, in case you need to save anything using Go code, the process looks very similar to what we've done above, when reading data from the storage:

secretData := map[string]interface{}{
    "value": "world",
    "foo": "bar",
    "age": "-1,
}
_, err = c.Logical().Write(secretKey, secretData)
...

Summary

As you can see, accessing Vault from Go code doesn't require much effort, given the fact that all the heavy (and menial, HTTP-related) work is done by the client that comes with Vault itself. To use it in your own application, you need a bit of initialization, after which it is just calling simple and self-explanatory functions.

While there are still things to come, you can check out the source code of the app I've been using to play around with Vault and store some of my darkest secrets, slomek/walter, the full source code is available on Github.