Skip to content

Commit

Permalink
Support for reading v2 secrets
Browse files Browse the repository at this point in the history
  • Loading branch information
Mongey committed Aug 7, 2018
1 parent 8b0a92e commit 5b3eaff
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 2 deletions.
13 changes: 11 additions & 2 deletions vault/data_source_generic_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ func genericSecretDataSource() *schema.Resource {
Description: "Full path from which a secret will be read.",
},

"version": {
Type: schema.TypeInt,
Required: false,
Optional: true,
Default: -1,
},

"data_json": {
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -65,9 +72,11 @@ func genericSecretDataSourceRead(d *schema.ResourceData, meta interface{}) error
client := meta.(*api.Client)

path := d.Get("path").(string)
version := d.Get("version").(int)

log.Printf("[DEBUG] Reading %s v%d from Vault", path, version)

log.Printf("[DEBUG] Reading %s from Vault", path)
secret, err := client.Logical().Read(path)
secret, err := versionedSecret(version, path, client)
if err != nil {
return fmt.Errorf("error reading from Vault: %s", err)
}
Expand Down
184 changes: 184 additions & 0 deletions vault/kv_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package vault

import (
"fmt"
"io"
"path"
"strings"

"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/helper/strutil"
)

func versionedSecret(requestedVersion int, path string, client *api.Client) (*api.Secret, error) {
mountPath, v2, err := isKVv2(path, client)
if err != nil {
return nil, err
}

var versionParam map[string]string

if v2 {
path = addPrefixToVKVPath(path, mountPath, "data")
if err != nil {
return nil, err
}

if requestedVersion > 0 {
versionParam = map[string]string{
"version": fmt.Sprintf("%d", requestedVersion),
}
}
}

secret, err := kvReadRequest(client, path, versionParam)

if err != nil {
return nil, err
}

if v2 {
// This is a v2, grab the data field
if data, ok := secret.Data["data"]; ok && data != nil {
if dataMap, ok := data.(map[string]interface{}); ok {
secret.Data = dataMap
}
}
}

return secret, nil
}

func kvReadRequest(client *api.Client, path string, params map[string]string) (*api.Secret, error) {
r := client.NewRequest("GET", "/v1/"+path)
for k, v := range params {
r.Params.Set(k, v)
}
resp, err := client.RawRequest(r)
if resp != nil {
defer resp.Body.Close()
}
if resp != nil && resp.StatusCode == 404 {
secret, parseErr := api.ParseSecret(resp.Body)
switch parseErr {
case nil:
case io.EOF:
return nil, nil
default:
return nil, err
}
if secret != nil && (len(secret.Warnings) > 0 || len(secret.Data) > 0) {
return secret, nil
}
return nil, nil
}
if err != nil {
return nil, err
}

return api.ParseSecret(resp.Body)
}

func kvPreflightVersionRequest(client *api.Client, path string) (string, int, error) {
// We don't want to use a wrapping call here so save any custom value and
// restore after
currentWrappingLookupFunc := client.CurrentWrappingLookupFunc()
client.SetWrappingLookupFunc(nil)
defer client.SetWrappingLookupFunc(currentWrappingLookupFunc)

r := client.NewRequest("GET", "/v1/sys/internal/ui/mounts/"+path)
resp, err := client.RawRequest(r)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
// If we get a 404 we are using an older version of vault, default to
// version 1
if resp != nil && resp.StatusCode == 404 {
return "", 1, nil
}

return "", 0, err
}

secret, err := api.ParseSecret(resp.Body)
if err != nil {
return "", 0, err
}
var mountPath string
if mountPathRaw, ok := secret.Data["path"]; ok {
mountPath = mountPathRaw.(string)
}
options := secret.Data["options"]
if options == nil {
return mountPath, 1, nil
}
versionRaw := options.(map[string]interface{})["version"]
if versionRaw == nil {
return mountPath, 1, nil
}
version := versionRaw.(string)
switch version {
case "", "1":
return mountPath, 1, nil
case "2":
return mountPath, 2, nil
}

return mountPath, 1, nil
}

func isKVv2(path string, client *api.Client) (string, bool, error) {
mountPath, version, err := kvPreflightVersionRequest(client, path)
if err != nil {
return "", false, err
}

return mountPath, version == 2, nil
}

func addPrefixToVKVPath(p, mountPath, apiPrefix string) string {
switch {
case p == mountPath, p == strings.TrimSuffix(mountPath, "/"):
return path.Join(mountPath, apiPrefix)
default:
p = strings.TrimPrefix(p, mountPath)
return path.Join(mountPath, apiPrefix, p)
}
}

func getHeaderForMap(header string, data map[string]interface{}) string {
maxKey := 0
for k := range data {
if len(k) > maxKey {
maxKey = len(k)
}
}

// 4 for the column spaces and 5 for the len("value")
totalLen := maxKey + 4 + 5

equalSigns := totalLen - (len(header) + 2)

// If we have zero or fewer equal signs bump it back up to two on either
// side of the header.
if equalSigns <= 0 {
equalSigns = 4
}

// If the number of equal signs is not divisible by two add a sign.
if equalSigns%2 != 0 {
equalSigns = equalSigns + 1
}

return fmt.Sprintf("%s %s %s", strings.Repeat("=", equalSigns/2), header, strings.Repeat("=", equalSigns/2))
}

func kvParseVersionsFlags(versions []string) []string {
versionsOut := make([]string, 0, len(versions))
for _, v := range versions {
versionsOut = append(versionsOut, strutil.ParseStringSlice(v, ",")...)
}

return versionsOut
}
3 changes: 3 additions & 0 deletions website/docs/d/generic_secret.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ default, this should be prefixed with `secret/`. Reading from other backends
with this data source is possible; consult each backend's documentation
to see which endpoints support the `GET` method.

* `version` - The version of the secret you want to read. Defaults to the
latest. Only supported when reading from a `v2` path.

## Required Vault Capabilities

Use of this resource requires the `read` capability on the given path.
Expand Down

0 comments on commit 5b3eaff

Please sign in to comment.