A simple client library generated from OpenAPI
specification file to interact with HashiCorp
Vault.
Warning: This library is currently marked as EXPERIMENTAL. Please try it out and give us feedback! Please do not use it in production.
Warning: The openapi.json file included in this repository is NOT the official Vault
OpenAPI
specification.
- Installation
- Examples
- Getting Started
- Authentication
- Using Generic Accessors
- Using Generated Methods
- Modifying Requests
- Error Handling
- Using TLS
- Using TLS with Client-side Certificate Authentication
- Loading Configuration from Environment Variables
- Logging Requests & Responses with Request/Response Callbacks
- Enforcing Read-your-writes Replication Semantics
- Building the Library
- Under Development
- Documentation for API Endpoints
go get github.com/hashicorp/vault-client-go
Here is a simple example of using the library to read and write your first
secret. For the sake of simplicity, we are authenticating with a root token.
This example works with a Vault server running in -dev
mode:
vault server -dev -dev-root-token-id="my-token"
package main
import (
"context"
"log"
"time"
"github.com/hashicorp/vault-client-go"
"github.com/hashicorp/vault-client-go/schema"
)
func main() {
ctx := context.Background()
// prepare a client with the given base address
client, err := vault.New(
vault.WithBaseAddress("http://127.0.0.1:8200"),
vault.WithRequestTimeout(30*time.Second),
)
if err != nil {
log.Fatal(err)
}
// authenticate with a root token (insecure)
if err := client.SetToken("my-token"); err != nil {
log.Fatal(err)
}
// write a secret
_, err = client.Secrets.KVv2Write(ctx, "my-secret", schema.KVv2WriteRequest{
Data: map[string]any{
"password1": "abc123",
"password2": "correct horse battery staple",
},
})
if err != nil {
log.Fatal(err)
}
log.Println("secret written successfully")
// read a secret
s, err := client.Secrets.KVv2Read(ctx, "my-secret")
if err != nil {
log.Fatal(err)
}
log.Println("secret retrieved:", s.Data)
}
In the previous example we used an insecure (root token) authentication method.
For production applications, it is recommended to use approle or
one of the platform-specific authentication methods instead (e.g.
Kubernetes, AWS, Azure, etc.). The
functions to access these authentication methods are automatically generated
under client.Auth
. Below is an example of how to authenticate using approle
authentication method. Please refer to the approle documentation
for more details.
resp, err := client.Auth.AppRoleLogin(
ctx,
schema.AppRoleLoginRequest{
RoleId: os.Getenv("MY_APPROLE_ROLE_ID"),
SecretId: os.Getenv("MY_APPROLE_SECRET_ID"),
},
vault.WithMountPath("my/approle/path"), // optional, defaults to "approle"
)
if err != nil {
log.Fatal(err)
}
if err := client.SetToken(resp.Auth.ClientToken); err != nil {
log.Fatal(err)
}
The secret identifier is often delivered as a wrapped token. In this case, you should unwrap it first as demonstrated here.
The library provides the following generic accessors which let you read, modify, and delete an arbitrary path within Vault:
client.Read(...)
client.ReadWithParameters(...)
client.ReadRaw(...)
client.ReadRawWithParameters(...)
client.Write(...)
client.WriteFromBytes(...)
client.WriteFromReader(...)
client.List(...)
client.Delete(...)
client.DeleteWithParameters(...)
For example, client.Secrets.KVv2Write(...)
from
Getting Started section could be rewritten using a generic
client.Write(...)
like so:
_, err = client.Write(ctx, "/secret/data/my-secret", map[string]any{
"data": map[string]any{
"password1": "abc123",
"password2": "correct horse battery staple",
},
})
The library has a number of generated methods corresponding to the known Vault API endpoints. They are organized in four catagories:
client.Auth // authentication-related methods
client.Secrets // methods dealing with secrets engines
client.Identity // identity-related methods
client.System // various system-wide calls
Below is an example of accessing a generated System.ReadMounts
method
(equivalent to vault secrets list
or GET /v1/sys/mounts
):
resp, err := client.System.ReadMounts(ctx)
if err != nil {
log.Fatal(err)
}
for engine := range resp.Data {
log.Println(engine)
}
Note: the
response.Data
is currently returned as simplemap[string]any
maps. Structured (strongly typed) responses are coming soon!
You can modify the requests in one of two ways, either at the client level or by decorating individual requests:
// all subsequent requests will use the given token & namespace
_ = client.SetToken("my-token")
_ = client.SetNamespace("my-namespace")
// per-request decorators take precedence over the client-level settings
resp, _ = client.Secrets.KVv2Read(
ctx,
"my-secret",
vault.WithToken("request-specific-token"),
vault.WithNamespace("request-specific-namespace"),
)
Vault plugins can be mounted at arbitrary mount paths using
-path
command-line argument:
vault secrets enable -path=my/mount/path kv-v2
To accomodate this behavior, the requests defined under client.Auth
and
client.Secrets
can be offset with mount path overrides using the following
syntax:
// Equivalent to client.Read(ctx, "my/mount/path/data/my-secret")
secret, err := client.Secrets.KVv2Read(
ctx,
"my-secret",
vault.WithMountPath("my/mount/path"),
)
Please refer to the response-wrapping documentation for more background information.
// wrap the response with a 5 minute TTL
resp, _ := client.Secrets.KVv2Read(
ctx,
"my-secret",
vault.WithResponseWrapping(5*time.Minute),
)
wrapped := resp.WrapInfo.Token
// unwrap the response (usually done elsewhere)
unwrapped, _ := vault.Unwrap[map[string]any](ctx, client, wrapped)
There are a couple specialized error types that the client can return:
ResponseError
is the error returned when Vault responds with a status code outside of the 200 - 399 range.RedirectError
is the error returned when the client fails to process a redirect response.
The client also provides a convenience function vault.IsErrorStatus(...)
to
simplify error handling:
s, err := client.Secrets.KVv2Read(ctx, "my-secret")
if err != nil {
if vault.IsErrorStatus(err, http.StatusForbidden) {
// special handling for 403 errors
}
if vault.IsErrorStatus(err, http.StatusNotFound) {
// special handling for 404 errors
}
return err
}
To enable TLS, simply specify the location of the Vault server's CA certificate file in the configuration:
tls := vault.TLSConfiguration{}
tls.ServerCertificate.FromFile = "/tmp/vault-ca.pem"
client, err := vault.New(
vault.WithBaseAddress("https://localhost:8200"),
vault.WithTLS(tls),
)
if err != nil {
log.Fatal(err)
}
...
You can test this with a -dev-tls
Vault server:
vault server -dev-tls -dev-root-token-id="my-token"
tls := vault.TLSConfiguration{}
tls.ServerCertificate.FromFile = "/tmp/vault-ca.pem"
tls.ClientCertificate.FromFile = "/tmp/client-cert.pem"
tls.ClientCertificateKey.FromFile = "/tmp/client-cert-key.pem"
client, err := vault.New(
vault.WithBaseAddress("https://localhost:8200"),
vault.WithTLS(tls),
)
if err != nil {
log.Fatal(err)
}
resp, err := client.Auth.CertificatesLogin(ctx, schema.CertificatesLoginRequest{
Name: "my-cert",
})
if err != nil {
log.Fatal(err)
}
if err := client.SetToken(resp.Auth.ClientToken); err != nil {
log.Fatal(err)
}
Note: this is a temporary solution using a generated method. The user experience will be improved with the introduction of auth wrappers.
client, err := vault.New(
vault.WithEnvironment(),
)
if err != nil {
log.Fatal(err)
}
export VAULT_ADDR=http://localhost:8200
export VAULT_TOKEN=my-token
go run main.go
client.SetRequestCallbacks(func(req *http.Request) {
// log req
})
client.SetResponseCallbacks(func(req *http.Request, resp *http.Response) {
// log req, resp
})
Alternatively, vault.WithRequestCallbacks(..)
/ vault.WithResponseCallbacks(..)
may be used to inject callbacks for individual requests.
Detailed background information of the read-after-write consistency problem can be found in the consistency and replication documentation pages.
You can enforce read-your-writes semantics for individual requests through callbacks:
var state string
// write
_, err := client.Secrets.KVv2Write(
ctx,
"my-secret",
schema.KVv2WriteRequest{
Data: map[string]any{
"password1": "abc123",
"password2": "correct horse battery staple",
},
}
vault.WithResponseCallbacks(
vault.RecordReplicationState(
&state,
),
),
)
// read
secret, err := client.Secrets.KVv2Read(
ctx,
"my-secret",
vault.WithRequestCallbacks(
vault.RequireReplicationStates(
&state,
),
),
)
Alternatively, enforce read-your-writes semantics for all requests using the following setting:
client, err := vault.New(
vault.WithBaseAddress("https://localhost:8200"),
vault.WithEnforceReadYourWritesConsistency(),
)
The vast majority of the code, including the client's endpoints, requests and
responses is generated from the OpenAPI
specification file
v1.13.0 using openapi-generator
. If you make any changes
to the underlying templates (generate/templates/*
), make sure to regenerate
the files by running the following:
make regen && go build
This library is currently under active development. Below is a list of high-level features that have been implemented:
- TLS
- Read/Write/Delete/List base accessors
- Automatic retries on errors (using go-retryablehttp)
- Custom redirect logic
- Client-side rate limiting
- Vault-specific headers (
X-Vault-Token
,X-Vault-Namespace
, etc.) and custom headers - Request/Response callbacks
- Environment variables for configuration
- Read-your-writes semantics
- Thread-safe cloning and client modifications
- Response wrapping & unwrapping
- CI/CD pipelines
The following features are coming soon:
- Structured responses (as part of the specification file)
- Testing framework
- Authentication wrappers
- Other helpers & wrappers (KV, SSH, Monitor, Plugins, LifetimeWatcher, etc.)