rebase: bump github.com/hashicorp/vault/api from 1.5.0 to 1.6.0

Bumps [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/hashicorp/vault/releases)
- [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/vault/compare/v1.5.0...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/vault/api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot] 2022-05-30 20:20:59 +00:00 committed by mergify[bot]
parent 01923ab166
commit 3c0e1b4970
34 changed files with 1186 additions and 260 deletions

10
go.mod
View File

@ -15,7 +15,7 @@ require (
github.com/golang/protobuf v1.5.2
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/hashicorp/vault/api v1.5.0
github.com/hashicorp/vault/api v1.6.0
github.com/kubernetes-csi/csi-lib-utils v0.11.0
github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0
github.com/libopenstorage/secrets v0.0.0-20210908194121-a1d19aa9713a
@ -86,15 +86,15 @@ require (
github.com/hashicorp/go-retryablehttp v0.6.6 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.5 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/hashicorp/go-version v1.2.0 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/vault v1.4.2 // indirect
github.com/hashicorp/vault/sdk v0.4.1 // indirect
github.com/hashicorp/vault/sdk v0.5.0 // indirect
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
@ -108,7 +108,7 @@ require (
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.4.2 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.1 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect

18
go.sum
View File

@ -592,12 +592,14 @@ github.com/hashicorp/go-secure-stdlib/base62 v0.1.1 h1:6KMBnfEv0/kLAz0O76sliN5mX
github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 h1:cCRo8gK7oq6A2L6LICkUZ+/a5rLiRXFMf1Qd4xSwxTc=
github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 h1:78ki3QBevHwYrVxnyVeaEz+7WtifHhauYF23es/0KlI=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.5 h1:MBgwAFPUbfuI0+tmDU/aeM1MARvdbqWmiieXIalKqDE=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.5/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
github.com/hashicorp/go-secure-stdlib/password v0.1.1 h1:6JzmBqXprakgFEHwBgdchsjaA9x3GyjdI568bXKxa60=
github.com/hashicorp/go-secure-stdlib/password v0.1.1/go.mod h1:9hH302QllNwu1o2TGYtSk8I8kTAN0ca1EHpwhm5Mmzo=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 h1:nd0HIW15E6FG1MsnArYaHfuw9C2zgzM8LxkG5Ty/788=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1 h1:Yc026VyMyIpq1UWRnakHRG01U8fJm+nEfEmjoAb00n8=
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
@ -662,8 +664,8 @@ github.com/hashicorp/vault/api v1.0.5-0.20191122173911-80fcc7907c78/go.mod h1:Uf
github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820/go.mod h1:3f12BMfgDGjTsTtIUj+ZKZwSobQpZtYGFIEehOv5z1o=
github.com/hashicorp/vault/api v1.0.5-0.20200317185738-82f498082f02/go.mod h1:3f12BMfgDGjTsTtIUj+ZKZwSobQpZtYGFIEehOv5z1o=
github.com/hashicorp/vault/api v1.0.5-0.20200902155336-f9d5ce5a171a/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk=
github.com/hashicorp/vault/api v1.5.0 h1:Bp6yc2bn7CWkOrVIzFT/Qurzx528bdavF3nz590eu28=
github.com/hashicorp/vault/api v1.5.0/go.mod h1:LkMdrZnWNrFaQyYYazWVn7KshilfDidgVBq6YiTq/bM=
github.com/hashicorp/vault/api v1.6.0 h1:B8UUYod1y1OoiGHq9GtpiqSnGOUEWHaA26AY8RQEDY4=
github.com/hashicorp/vault/api v1.6.0/go.mod h1:h1K70EO2DgnBaTz5IsL6D5ERsNt5Pce93ueVS2+t0Xc=
github.com/hashicorp/vault/sdk v0.1.8/go.mod h1:tHZfc6St71twLizWNHvnnbiGFo1aq0eD2jGPLtP8kAU=
github.com/hashicorp/vault/sdk v0.1.14-0.20190730042320-0dc007d98cc8/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
github.com/hashicorp/vault/sdk v0.1.14-0.20191108161836-82f2b5571044/go.mod h1:PcekaFGiPJyHnFy+NZhP6ll650zEw51Ag7g/YEa+EOU=
@ -673,8 +675,8 @@ github.com/hashicorp/vault/sdk v0.1.14-0.20200317185738-82f498082f02/go.mod h1:W
github.com/hashicorp/vault/sdk v0.1.14-0.20200427170607-03332aaf8d18/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
github.com/hashicorp/vault/sdk v0.1.14-0.20200429182704-29fce8f27ce4/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
github.com/hashicorp/vault/sdk v0.4.1 h1:3SaHOJY687jY1fnB61PtL0cOkKItphrbLmux7T92HBo=
github.com/hashicorp/vault/sdk v0.4.1/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0=
github.com/hashicorp/vault/sdk v0.5.0 h1:EED7p0OCU3OY5SAqJwSANofY1YKMytm+jDHDQ2EzGVQ=
github.com/hashicorp/vault/sdk v0.5.0/go.mod h1:UJZHlfwj7qUJG8g22CuxUgkdJouFrBNvBHCyx8XAPdo=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
@ -825,8 +827,8 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/pointerstructure v0.0.0-20190430161007-f252a8fd71c8/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"math"
"regexp"
"strconv"
"strings"
@ -92,6 +93,9 @@ func ParseCapacityString(in interface{}) (uint64, error) {
return cap, nil
}
// Parse a duration from an arbitrary value (a string or numeric value) into
// a time.Duration; when units are missing (such as when a numeric type is
// provided), the duration is assumed to be in seconds.
func ParseDurationSecond(in interface{}) (time.Duration, error) {
var dur time.Duration
jsonIn, ok := in.(json.Number)
@ -105,21 +109,23 @@ func ParseDurationSecond(in interface{}) (time.Duration, error) {
if inp == "" {
return dur, nil
}
if v, err := strconv.ParseInt(inp, 10, 64); err == nil {
return time.Duration(v) * time.Second, nil
}
if strings.HasSuffix(inp, "d") {
v, err := strconv.ParseInt(inp[:len(inp)-1], 10, 64)
if err != nil {
return dur, err
}
return time.Duration(v) * 24 * time.Hour, nil
}
var err error
// Look for a suffix otherwise its a plain second value
if strings.HasSuffix(inp, "s") || strings.HasSuffix(inp, "m") || strings.HasSuffix(inp, "h") || strings.HasSuffix(inp, "ms") {
dur, err = time.ParseDuration(inp)
if err != nil {
if dur, err = time.ParseDuration(inp); err != nil {
return dur, err
}
} else {
// Plain integer
secs, err := strconv.ParseInt(inp, 10, 64)
if err != nil {
return dur, err
}
dur = time.Duration(secs) * time.Second
}
case int:
dur = time.Duration(inp) * time.Second
case int32:
@ -145,6 +151,9 @@ func ParseDurationSecond(in interface{}) (time.Duration, error) {
return dur, nil
}
// Parse an absolute timestamp from the provided arbitrary value (string or
// numeric value). When an untyped numeric value is provided, it is assumed
// to be seconds from the Unix Epoch.
func ParseAbsoluteTime(in interface{}) (time.Time, error) {
var t time.Time
switch inp := in.(type) {
@ -193,6 +202,13 @@ func ParseAbsoluteTime(in interface{}) (time.Time, error) {
return t, nil
}
// ParseInt takes an arbitrary value (either a string or numeric type) and
// parses it as an int64 value. This value is assumed to be larger than the
// provided type, but cannot safely be cast.
//
// When the end value is bounded (such as an int value), it is recommended
// to instead call SafeParseInt or SafeParseIntRange to safely cast to a
// more restrictive type.
func ParseInt(in interface{}) (int64, error) {
var ret int64
jsonIn, ok := in.(json.Number)
@ -230,6 +246,104 @@ func ParseInt(in interface{}) (int64, error) {
return ret, nil
}
// ParseDirectIntSlice behaves similarly to ParseInt, but accepts typed
// slices, returning a slice of int64s.
//
// If the starting value may not be in slice form (e.g.. a bare numeric value
// could be provided), it is suggested to call ParseIntSlice instead.
func ParseDirectIntSlice(in interface{}) ([]int64, error) {
var ret []int64
switch in.(type) {
case []int:
for _, v := range in.([]int) {
ret = append(ret, int64(v))
}
case []int32:
for _, v := range in.([]int32) {
ret = append(ret, int64(v))
}
case []int64:
// For consistency to ensure callers can always modify ret without
// impacting in.
for _, v := range in.([]int64) {
ret = append(ret, v)
}
case []uint:
for _, v := range in.([]uint) {
ret = append(ret, int64(v))
}
case []uint32:
for _, v := range in.([]uint32) {
ret = append(ret, int64(v))
}
case []uint64:
for _, v := range in.([]uint64) {
ret = append(ret, int64(v))
}
case []json.Number:
for _, v := range in.([]json.Number) {
element, err := ParseInt(v)
if err != nil {
return nil, err
}
ret = append(ret, element)
}
case []string:
for _, v := range in.([]string) {
element, err := ParseInt(v)
if err != nil {
return nil, err
}
ret = append(ret, element)
}
default:
return nil, errors.New("could not parse value from input")
}
return ret, nil
}
// ParseIntSlice is a helper function for handling upgrades of optional
// slices; that is, if the API accepts a type similar to <int|[]int>,
// nicely handle the common cases of providing only an int-ish, providing
// an actual slice of int-ishes, or providing a comma-separated list of
// numbers.
//
// When []int64 is not the desired final type (or the values should be
// range-bound), it is suggested to call SafeParseIntSlice or
// SafeParseIntSliceRange instead.
func ParseIntSlice(in interface{}) ([]int64, error) {
if ret, err := ParseInt(in); err == nil {
return []int64{ret}, nil
}
if ret, err := ParseDirectIntSlice(in); err == nil {
return ret, nil
}
if strings, err := ParseCommaStringSlice(in); err == nil {
var ret []int64
for _, v := range strings {
if v == "" {
// Ignore empty fields
continue
}
element, err := ParseInt(v)
if err != nil {
return nil, err
}
ret = append(ret, element)
}
return ret, nil
}
return nil, errors.New("could not parse value from input")
}
// Parses the provided arbitrary value as a boolean-like value.
func ParseBool(in interface{}) (bool, error) {
var result bool
if err := mapstructure.WeakDecode(in, &result); err != nil {
@ -238,6 +352,7 @@ func ParseBool(in interface{}) (bool, error) {
return result, nil
}
// Parses the provided arbitrary value as a string.
func ParseString(in interface{}) (string, error) {
var result string
if err := mapstructure.WeakDecode(in, &result); err != nil {
@ -246,7 +361,13 @@ func ParseString(in interface{}) (string, error) {
return result, nil
}
// Parses the provided string-like value as a comma-separated list of values.
func ParseCommaStringSlice(in interface{}) ([]string, error) {
jsonIn, ok := in.(json.Number)
if ok {
in = jsonIn.String()
}
rawString, ok := in.(string)
if ok && rawString == "" {
return []string{}, nil
@ -267,6 +388,7 @@ func ParseCommaStringSlice(in interface{}) ([]string, error) {
return strutil.TrimStrings(result), nil
}
// Parses the specified value as one or more addresses, separated by commas.
func ParseAddrs(addrs interface{}) ([]*sockaddr.SockAddrMarshaler, error) {
out := make([]*sockaddr.SockAddrMarshaler, 0)
stringAddrs := make([]string, 0)
@ -306,3 +428,75 @@ func ParseAddrs(addrs interface{}) ([]*sockaddr.SockAddrMarshaler, error) {
return out, nil
}
// Parses the provided arbitrary value (see ParseInt), ensuring it is within
// the specified range (inclusive of bounds). If this range corresponds to a
// smaller type, the returned value can then be safely cast without risking
// overflow.
func SafeParseIntRange(in interface{}, min int64, max int64) (int64, error) {
raw, err := ParseInt(in)
if err != nil {
return 0, err
}
if raw < min || raw > max {
return 0, fmt.Errorf("error parsing int value; out of range [%v to %v]: %v", min, max, raw)
}
return raw, nil
}
// Parses the specified arbitrary value (see ParseInt), ensuring that the
// resulting value is within the range for an int value. If no error occurred,
// the caller knows no overflow occurred.
func SafeParseInt(in interface{}) (int, error) {
raw, err := SafeParseIntRange(in, math.MinInt, math.MaxInt)
return int(raw), err
}
// Parses the provided arbitrary value (see ParseIntSlice) into a slice of
// int64 values, ensuring each is within the specified range (inclusive of
// bounds). If this range corresponds to a smaller type, the returned value
// can then be safely cast without risking overflow.
//
// If elements is positive, it is used to ensure the resulting slice is
// bounded above by that many number of elements (inclusive).
func SafeParseIntSliceRange(in interface{}, minValue int64, maxValue int64, elements int) ([]int64, error) {
raw, err := ParseIntSlice(in)
if err != nil {
return nil, err
}
if elements > 0 && len(raw) > elements {
return nil, fmt.Errorf("error parsing value from input: got %v but expected at most %v elements", len(raw), elements)
}
for index, value := range raw {
if value < minValue || value > maxValue {
return nil, fmt.Errorf("error parsing value from input: element %v was outside of range [%v to %v]: %v", index, minValue, maxValue, value)
}
}
return raw, nil
}
// Parses the provided arbitrary value (see ParseIntSlice) into a slice of
// int values, ensuring the each resulting value in the slice is within the
// range for an int value. If no error occurred, the caller knows no overflow
// occurred.
//
// If elements is positive, it is used to ensure the resulting slice is
// bounded above by that many number of elements (inclusive).
func SafeParseIntSlice(in interface{}, elements int) ([]int, error) {
raw, err := SafeParseIntSliceRange(in, math.MinInt, math.MaxInt, elements)
if err != nil || raw == nil {
return nil, err
}
var result = make([]int, len(raw))
for _, element := range raw {
result = append(result, int(element))
}
return result, nil
}

View File

@ -230,16 +230,16 @@ func TrimStrings(items []string) []string {
// strings. This also may convert the items in the slice to lower case and
// returns a sorted slice.
func RemoveDuplicates(items []string, lowercase bool) []string {
itemsMap := map[string]bool{}
itemsMap := make(map[string]struct{}, len(items))
for _, item := range items {
item = strings.TrimSpace(item)
if lowercase {
item = strings.ToLower(item)
}
if item == "" {
continue
}
itemsMap[item] = true
if lowercase {
item = strings.ToLower(item)
}
itemsMap[item] = struct{}{}
}
items = make([]string, 0, len(itemsMap))
for item := range itemsMap {
@ -254,18 +254,21 @@ func RemoveDuplicates(items []string, lowercase bool) []string {
// In all cases, strings are compared after trimming whitespace
// If caseInsensitive, strings will be compared after ToLower()
func RemoveDuplicatesStable(items []string, caseInsensitive bool) []string {
itemsMap := make(map[string]bool, len(items))
itemsMap := make(map[string]struct{}, len(items))
deduplicated := make([]string, 0, len(items))
for _, item := range items {
key := strings.TrimSpace(item)
if _, ok := itemsMap[key]; ok || key == "" {
continue
}
if caseInsensitive {
key = strings.ToLower(key)
}
if key == "" || itemsMap[key] {
if _, ok := itemsMap[key]; ok {
continue
}
itemsMap[key] = true
itemsMap[key] = struct{}{}
deduplicated = append(deduplicated, item)
}
return deduplicated
@ -299,17 +302,18 @@ func EquivalentSlices(a, b []string) bool {
}
// First we'll build maps to ensure unique values
mapA := map[string]bool{}
mapB := map[string]bool{}
mapA := make(map[string]struct{}, len(a))
mapB := make(map[string]struct{}, len(b))
for _, keyA := range a {
mapA[keyA] = true
mapA[keyA] = struct{}{}
}
for _, keyB := range b {
mapB[keyB] = true
mapB[keyB] = struct{}{}
}
// Now we'll build our checking slices
var sortedA, sortedB []string
sortedA := make([]string, 0, len(mapA))
sortedB := make([]string, 0, len(mapB))
for keyA := range mapA {
sortedA = append(sortedA, keyA)
}
@ -434,24 +438,22 @@ func Difference(a, b []string, lowercase bool) []string {
a = RemoveDuplicates(a, lowercase)
b = RemoveDuplicates(b, lowercase)
itemsMap := map[string]bool{}
itemsMap := map[string]struct{}{}
for _, aVal := range a {
itemsMap[aVal] = true
itemsMap[aVal] = struct{}{}
}
// Perform difference calculation
for _, bVal := range b {
if _, ok := itemsMap[bVal]; ok {
itemsMap[bVal] = false
delete(itemsMap, bVal)
}
}
items := []string{}
for item, exists := range itemsMap {
if exists {
for item := range itemsMap {
items = append(items, item)
}
}
sort.Strings(items)
return items
}

View File

@ -31,16 +31,82 @@ func (a *Auth) Login(ctx context.Context, authMethod AuthMethod) (*Secret, error
if authMethod == nil {
return nil, fmt.Errorf("no auth method provided for login")
}
return a.login(ctx, authMethod)
}
authSecret, err := authMethod.Login(ctx, a.c)
// MFALogin is a wrapper that helps satisfy Vault's MFA implementation.
// If optional credentials are provided a single-phase login will be attempted
// and the resulting Secret will contain a ClientToken if the authentication is successful.
// The client's token will also be set accordingly.
//
// If no credentials are provided a two-phase MFA login will be assumed and the resulting
// Secret will have a MFARequirement containing the MFARequestID to be used in a follow-up
// call to `sys/mfa/validate` or by passing it to the method (*Auth).MFAValidate.
func (a *Auth) MFALogin(ctx context.Context, authMethod AuthMethod, creds ...string) (*Secret, error) {
if len(creds) > 0 {
a.c.SetMFACreds(creds)
return a.login(ctx, authMethod)
}
return a.twoPhaseMFALogin(ctx, authMethod)
}
// MFAValidate validates an MFA request using the appropriate payload and a secret containing
// Auth.MFARequirement, like the one returned by MFALogin when credentials are not provided.
// Upon successful validation the client token will be set accordingly.
//
// The Secret returned is the authentication secret, which if desired can be
// passed as input to the NewLifetimeWatcher method in order to start
// automatically renewing the token.
func (a *Auth) MFAValidate(ctx context.Context, mfaSecret *Secret, payload map[string]interface{}) (*Secret, error) {
if mfaSecret == nil || mfaSecret.Auth == nil || mfaSecret.Auth.MFARequirement == nil {
return nil, fmt.Errorf("secret does not contain MFARequirements")
}
s, err := a.c.Sys().MFAValidateWithContext(ctx, mfaSecret.Auth.MFARequirement.GetMFARequestID(), payload)
if err != nil {
return nil, err
}
return a.checkAndSetToken(s)
}
// login performs the (*AuthMethod).Login() with the configured client and checks that a ClientToken is returned
func (a *Auth) login(ctx context.Context, authMethod AuthMethod) (*Secret, error) {
s, err := authMethod.Login(ctx, a.c)
if err != nil {
return nil, fmt.Errorf("unable to log in to auth method: %w", err)
}
if authSecret == nil || authSecret.Auth == nil || authSecret.Auth.ClientToken == "" {
return nil, fmt.Errorf("login response from auth method did not return client token")
return a.checkAndSetToken(s)
}
a.c.SetToken(authSecret.Auth.ClientToken)
return authSecret, nil
// twoPhaseMFALogin performs the (*AuthMethod).Login() with the configured client
// and checks that an MFARequirement is returned
func (a *Auth) twoPhaseMFALogin(ctx context.Context, authMethod AuthMethod) (*Secret, error) {
s, err := authMethod.Login(ctx, a.c)
if err != nil {
return nil, fmt.Errorf("unable to log in: %w", err)
}
if s == nil || s.Auth == nil || s.Auth.MFARequirement == nil {
if s != nil {
s.Warnings = append(s.Warnings, "expected secret to contain MFARequirements")
}
return s, fmt.Errorf("assumed two-phase MFA login, returned secret is missing MFARequirements")
}
return s, nil
}
func (a *Auth) checkAndSetToken(s *Secret) (*Secret, error) {
if s == nil || s.Auth == nil || s.Auth.ClientToken == "" {
if s != nil {
s.Warnings = append(s.Warnings, "expected secret to contain ClientToken")
}
return s, fmt.Errorf("response did not return ClientToken, client token not set")
}
a.c.SetToken(s.Auth.ClientToken)
return s, nil
}

View File

@ -36,6 +36,7 @@ const (
EnvVaultAddress = "VAULT_ADDR"
EnvVaultAgentAddr = "VAULT_AGENT_ADDR"
EnvVaultCACert = "VAULT_CACERT"
EnvVaultCACertBytes = "VAULT_CACERT_BYTES"
EnvVaultCAPath = "VAULT_CAPATH"
EnvVaultClientCert = "VAULT_CLIENT_CERT"
EnvVaultClientKey = "VAULT_CLIENT_KEY"
@ -50,6 +51,7 @@ const (
EnvVaultMFA = "VAULT_MFA"
EnvRateLimit = "VAULT_RATE_LIMIT"
EnvHTTPProxy = "VAULT_HTTP_PROXY"
EnvVaultProxyAddr = "VAULT_PROXY_ADDR"
HeaderIndex = "X-Vault-Index"
HeaderForward = "X-Vault-Forward"
HeaderInconsistent = "X-Vault-Inconsistent"
@ -142,6 +144,14 @@ type Config struct {
// with the same client. Cloning a client will not clone this value.
OutputCurlString bool
// OutputPolicy causes the actual request to return an error of type
// *OutputPolicyError. Type asserting the error message will display
// an example of the required policy HCL needed for the operation.
//
// Note: It is not thread-safe to set this and make concurrent requests
// with the same client. Cloning a client will not clone this value.
OutputPolicy bool
// curlCACert, curlCAPath, curlClientCert and curlClientKey are used to keep
// track of the name of the TLS certs and keys when OutputCurlString is set.
// Cloning a client will also not clone those values.
@ -172,9 +182,14 @@ type Config struct {
// used to communicate with Vault.
type TLSConfig struct {
// CACert is the path to a PEM-encoded CA cert file to use to verify the
// Vault server SSL certificate.
// Vault server SSL certificate. It takes precedence over CACertBytes
// and CAPath.
CACert string
// CACertBytes is a PEM-encoded certificate or bundle. It takes precedence
// over CAPath.
CACertBytes []byte
// CAPath is the path to a directory of PEM-encoded CA cert files to verify
// the Vault server SSL certificate.
CAPath string
@ -266,11 +281,12 @@ func (c *Config) configureTLS(t *TLSConfig) error {
return fmt.Errorf("both client cert and client key must be provided")
}
if t.CACert != "" || t.CAPath != "" {
if t.CACert != "" || len(t.CACertBytes) != 0 || t.CAPath != "" {
c.curlCACert = t.CACert
c.curlCAPath = t.CAPath
rootConfig := &rootcerts.Config{
CAFile: t.CACert,
CACertificate: t.CACertBytes,
CAPath: t.CAPath,
}
if err := rootcerts.ConfigureTLS(clientTLSConfig, rootConfig); err != nil {
@ -313,6 +329,7 @@ func (c *Config) ReadEnvironment() error {
var envAddress string
var envAgentAddress string
var envCACert string
var envCACertBytes []byte
var envCAPath string
var envClientCert string
var envClientKey string
@ -322,7 +339,7 @@ func (c *Config) ReadEnvironment() error {
var envMaxRetries *uint64
var envSRVLookup bool
var limit *rate.Limiter
var envHTTPProxy string
var envVaultProxy string
// Parse the environment variables
if v := os.Getenv(EnvVaultAddress); v != "" {
@ -343,6 +360,9 @@ func (c *Config) ReadEnvironment() error {
if v := os.Getenv(EnvVaultCACert); v != "" {
envCACert = v
}
if v := os.Getenv(EnvVaultCACertBytes); v != "" {
envCACertBytes = []byte(v)
}
if v := os.Getenv(EnvVaultCAPath); v != "" {
envCAPath = v
}
@ -392,12 +412,18 @@ func (c *Config) ReadEnvironment() error {
}
if v := os.Getenv(EnvHTTPProxy); v != "" {
envHTTPProxy = v
envVaultProxy = v
}
// VAULT_PROXY_ADDR supersedes VAULT_HTTP_PROXY
if v := os.Getenv(EnvVaultProxyAddr); v != "" {
envVaultProxy = v
}
// Configure the HTTP clients TLS configuration.
t := &TLSConfig{
CACert: envCACert,
CACertBytes: envCACertBytes,
CAPath: envCAPath,
ClientCert: envClientCert,
ClientKey: envClientKey,
@ -431,14 +457,14 @@ func (c *Config) ReadEnvironment() error {
c.Timeout = envClientTimeout
}
if envHTTPProxy != "" {
url, err := url.Parse(envHTTPProxy)
if envVaultProxy != "" {
u, err := url.Parse(envVaultProxy)
if err != nil {
return err
}
transport := c.HttpClient.Transport.(*http.Transport)
transport.Proxy = http.ProxyURL(url)
transport.Proxy = http.ProxyURL(u)
}
return nil
@ -576,7 +602,6 @@ func (c *Client) CloneConfig() *Config {
newConfig.CheckRetry = c.config.CheckRetry
newConfig.Logger = c.config.Logger
newConfig.Limiter = c.config.Limiter
newConfig.OutputCurlString = c.config.OutputCurlString
newConfig.SRVLookup = c.config.SRVLookup
newConfig.CloneHeaders = c.config.CloneHeaders
newConfig.CloneToken = c.config.CloneToken
@ -589,7 +614,7 @@ func (c *Client) CloneConfig() *Config {
return newConfig
}
// Sets the address of Vault in the client. The format of address should be
// SetAddress sets the address of Vault in the client. The format of address should be
// "<Scheme>://<Host>:<Port>". Setting this on a client will override the
// value of VAULT_ADDR environment variable.
func (c *Client) SetAddress(addr string) error {
@ -616,6 +641,16 @@ func (c *Client) Address() string {
return c.addr.String()
}
func (c *Client) SetCheckRedirect(f func(*http.Request, []*http.Request) error) {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
c.config.modifyLock.Lock()
defer c.config.modifyLock.Unlock()
c.config.HttpClient.CheckRedirect = f
}
// SetLimiter will set the rate limiter for this client.
// This method is thread-safe.
// rateLimit and burst are specified according to https://godoc.org/golang.org/x/time/rate#NewLimiter
@ -768,6 +803,24 @@ func (c *Client) SetOutputCurlString(curl bool) {
c.config.OutputCurlString = curl
}
func (c *Client) OutputPolicy() bool {
c.modifyLock.RLock()
defer c.modifyLock.RUnlock()
c.config.modifyLock.RLock()
defer c.config.modifyLock.RUnlock()
return c.config.OutputPolicy
}
func (c *Client) SetOutputPolicy(isSet bool) {
c.modifyLock.RLock()
defer c.modifyLock.RUnlock()
c.config.modifyLock.Lock()
defer c.config.modifyLock.Unlock()
c.config.OutputPolicy = isSet
}
// CurrentWrappingLookupFunc sets a lookup function that returns desired wrap TTLs
// for a given operation and path.
func (c *Client) CurrentWrappingLookupFunc() WrappingLookupFunc {
@ -808,11 +861,40 @@ func (c *Client) setNamespace(namespace string) {
c.headers.Set(consts.NamespaceHeaderName, namespace)
}
// ClearNamespace removes the namespace header if set.
func (c *Client) ClearNamespace() {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
if c.headers != nil {
c.headers.Del(consts.NamespaceHeaderName)
}
}
// Namespace returns the namespace currently set in this client. It will
// return an empty string if there is no namespace set.
func (c *Client) Namespace() string {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
if c.headers == nil {
return ""
}
return c.headers.Get(consts.NamespaceHeaderName)
}
// WithNamespace makes a shallow copy of Client, modifies it to use
// the given namespace, and returns it. Passing an empty string will
// temporarily unset the namespace.
func (c *Client) WithNamespace(namespace string) *Client {
c2 := *c
c2.modifyLock = sync.RWMutex{}
c2.headers = c.Headers()
if namespace == "" {
c2.ClearNamespace()
} else {
c2.SetNamespace(namespace)
}
return &c2
}
// Token returns the access token being used by this client. It will
// return the empty string if there is no token set.
@ -1000,7 +1082,6 @@ func (c *Client) clone(cloneHeaders bool) (*Client, error) {
CheckRetry: config.CheckRetry,
Logger: config.Logger,
Limiter: config.Limiter,
OutputCurlString: config.OutputCurlString,
AgentAddress: config.AgentAddress,
SRVLookup: config.SRVLookup,
CloneHeaders: config.CloneHeaders,
@ -1131,12 +1212,23 @@ func (c *Client) rawRequestWithContext(ctx context.Context, r *Request) (*Respon
checkRetry := c.config.CheckRetry
backoff := c.config.Backoff
httpClient := c.config.HttpClient
ns := c.headers.Get(consts.NamespaceHeaderName)
outputCurlString := c.config.OutputCurlString
outputPolicy := c.config.OutputPolicy
logger := c.config.Logger
c.config.modifyLock.RUnlock()
c.modifyLock.RUnlock()
// ensure that the most current namespace setting is used at the time of the call
// e.g. calls using (*Client).WithNamespace
switch ns {
case "":
r.Headers.Del(consts.NamespaceHeaderName)
default:
r.Headers.Set(consts.NamespaceHeaderName, ns)
}
for _, cb := range c.requestCallbacks {
cb(r)
}
@ -1176,6 +1268,14 @@ START:
return nil, LastOutputStringError
}
if outputPolicy {
LastOutputPolicyError = &OutputPolicyError{
method: req.Method,
path: strings.TrimPrefix(req.URL.Path, "/v1"),
}
return nil, LastOutputPolicyError
}
req.Request = req.Request.WithContext(ctx)
if backoff == nil {
@ -1268,20 +1368,31 @@ func (c *Client) httpRequestWithContext(ctx context.Context, r *Request) (*Respo
limiter := c.config.Limiter
httpClient := c.config.HttpClient
outputCurlString := c.config.OutputCurlString
outputPolicy := c.config.OutputPolicy
// add headers
if c.headers != nil {
for header, vals := range c.headers {
for _, val := range vals {
req.Header.Add(header, val)
}
}
// explicitly set the namespace header to current client
if ns := c.headers.Get(consts.NamespaceHeaderName); ns != "" {
r.Headers.Set(consts.NamespaceHeaderName, ns)
}
}
c.config.modifyLock.RUnlock()
c.modifyLock.RUnlock()
// OutputCurlString logic relies on the request type to be retryable.Request as
// OutputCurlString and OutputPolicy logic rely on the request type to be retryable.Request
if outputCurlString {
return nil, fmt.Errorf("output-curl-string is not implemented for this request")
}
if outputPolicy {
return nil, fmt.Errorf("output-policy is not implemented for this request")
}
req.URL.User = r.URL.User
req.URL.Scheme = r.URL.Scheme

View File

@ -113,7 +113,9 @@ type LifetimeWatcherInput struct {
// The new TTL, in seconds, that should be set on the lease. The TTL set
// here may or may not be honored by the vault server, based on Vault
// configuration or any associated max TTL values.
// configuration or any associated max TTL values. If specified, the
// minimum of this value and the remaining lease duration will be used
// for grace period calculations.
Increment int
// RenewBehavior controls what happens when a renewal errors or the
@ -257,7 +259,7 @@ func (r *LifetimeWatcher) doRenewWithOptions(tokenMode bool, nonRenewable bool,
initialTime := time.Now()
priorDuration := time.Duration(initLeaseDuration) * time.Second
r.calculateGrace(priorDuration)
r.calculateGrace(priorDuration, time.Duration(r.increment)*time.Second)
var errorBackoff backoff.BackOff
for {
@ -345,7 +347,7 @@ func (r *LifetimeWatcher) doRenewWithOptions(tokenMode bool, nonRenewable bool,
// extending. Once it stops extending, we've hit the max and need to
// rely on the grace duration.
if remainingLeaseDuration > priorDuration {
r.calculateGrace(remainingLeaseDuration)
r.calculateGrace(remainingLeaseDuration, time.Duration(r.increment)*time.Second)
}
priorDuration = remainingLeaseDuration
@ -373,16 +375,21 @@ func (r *LifetimeWatcher) doRenewWithOptions(tokenMode bool, nonRenewable bool,
}
}
// calculateGrace calculates the grace period based on a reasonable set of
// assumptions given the total lease time; it also adds some jitter to not have
// clients be in sync.
func (r *LifetimeWatcher) calculateGrace(leaseDuration time.Duration) {
if leaseDuration <= 0 {
// calculateGrace calculates the grace period based on the minimum of the
// remaining lease duration and the token increment value; it also adds some
// jitter to not have clients be in sync.
func (r *LifetimeWatcher) calculateGrace(leaseDuration, increment time.Duration) {
minDuration := leaseDuration
if minDuration > increment && increment > 0 {
minDuration = increment
}
if minDuration <= 0 {
r.grace = 0
return
}
leaseNanos := float64(leaseDuration.Nanoseconds())
leaseNanos := float64(minDuration.Nanoseconds())
jitterMax := 0.1 * leaseNanos
// For a given lease duration, we want to allow 80-90% of that to elapse,

View File

@ -323,7 +323,7 @@ func (c *Logical) UnwrapWithContext(ctx context.Context, wrappingToken string) (
c.c.SetToken(wrappingToken)
}
secret, err = c.Read(wrappedResponseLocation)
secret, err = c.ReadWithContext(ctx, wrappedResponseLocation)
if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("error reading %q: {{err}}", wrappedResponseLocation), err)
}

82
vendor/github.com/hashicorp/vault/api/output_policy.go generated vendored Normal file
View File

@ -0,0 +1,82 @@
package api
import (
"fmt"
"net/http"
"net/url"
"strings"
)
const (
ErrOutputPolicyRequest = "output a policy, please"
)
var LastOutputPolicyError *OutputPolicyError
type OutputPolicyError struct {
method string
path string
finalHCLString string
}
func (d *OutputPolicyError) Error() string {
if d.finalHCLString == "" {
p, err := d.buildSamplePolicy()
if err != nil {
return err.Error()
}
d.finalHCLString = p
}
return ErrOutputPolicyRequest
}
func (d *OutputPolicyError) HCLString() (string, error) {
if d.finalHCLString == "" {
p, err := d.buildSamplePolicy()
if err != nil {
return "", err
}
d.finalHCLString = p
}
return d.finalHCLString, nil
}
// Builds a sample policy document from the request
func (d *OutputPolicyError) buildSamplePolicy() (string, error) {
var capabilities []string
switch d.method {
case http.MethodGet, "":
capabilities = append(capabilities, "read")
case http.MethodPost, http.MethodPut:
capabilities = append(capabilities, "create")
capabilities = append(capabilities, "update")
case http.MethodPatch:
capabilities = append(capabilities, "patch")
case http.MethodDelete:
capabilities = append(capabilities, "delete")
case "LIST":
capabilities = append(capabilities, "list")
}
// sanitize, then trim the Vault address and v1 from the front of the path
path, err := url.PathUnescape(d.path)
if err != nil {
return "", fmt.Errorf("failed to unescape request URL characters: %v", err)
}
// determine whether to add sudo capability
if IsSudoPath(path) {
capabilities = append(capabilities, "sudo")
}
// the OpenAPI response has a / in front of each path,
// but policies need the path without that leading slash
path = strings.TrimLeft(path, "/")
capStr := strings.Join(capabilities, `", "`)
return fmt.Sprintf(
`path "%s" {
capabilities = ["%s"]
}`, path, capStr), nil
}

View File

@ -19,58 +19,68 @@ type OutputStringError struct {
TLSSkipVerify bool
ClientCACert, ClientCAPath string
ClientCert, ClientKey string
parsingError error
parsedCurlString string
finalCurlString string
}
func (d *OutputStringError) Error() string {
if d.parsedCurlString == "" {
d.parseRequest()
if d.parsingError != nil {
return d.parsingError.Error()
if d.finalCurlString == "" {
cs, err := d.buildCurlString()
if err != nil {
return err.Error()
}
d.finalCurlString = cs
}
return ErrOutputStringRequest
}
func (d *OutputStringError) parseRequest() {
func (d *OutputStringError) CurlString() (string, error) {
if d.finalCurlString == "" {
cs, err := d.buildCurlString()
if err != nil {
return "", err
}
d.finalCurlString = cs
}
return d.finalCurlString, nil
}
func (d *OutputStringError) buildCurlString() (string, error) {
body, err := d.Request.BodyBytes()
if err != nil {
d.parsingError = err
return
return "", err
}
// Build cURL string
d.parsedCurlString = "curl "
finalCurlString := "curl "
if d.TLSSkipVerify {
d.parsedCurlString += "--insecure "
finalCurlString += "--insecure "
}
if d.Request.Method != http.MethodGet {
d.parsedCurlString = fmt.Sprintf("%s-X %s ", d.parsedCurlString, d.Request.Method)
finalCurlString = fmt.Sprintf("%s-X %s ", finalCurlString, d.Request.Method)
}
if d.ClientCACert != "" {
clientCACert := strings.Replace(d.ClientCACert, "'", "'\"'\"'", -1)
d.parsedCurlString = fmt.Sprintf("%s--cacert '%s' ", d.parsedCurlString, clientCACert)
finalCurlString = fmt.Sprintf("%s--cacert '%s' ", finalCurlString, clientCACert)
}
if d.ClientCAPath != "" {
clientCAPath := strings.Replace(d.ClientCAPath, "'", "'\"'\"'", -1)
d.parsedCurlString = fmt.Sprintf("%s--capath '%s' ", d.parsedCurlString, clientCAPath)
finalCurlString = fmt.Sprintf("%s--capath '%s' ", finalCurlString, clientCAPath)
}
if d.ClientCert != "" {
clientCert := strings.Replace(d.ClientCert, "'", "'\"'\"'", -1)
d.parsedCurlString = fmt.Sprintf("%s--cert '%s' ", d.parsedCurlString, clientCert)
finalCurlString = fmt.Sprintf("%s--cert '%s' ", finalCurlString, clientCert)
}
if d.ClientKey != "" {
clientKey := strings.Replace(d.ClientKey, "'", "'\"'\"'", -1)
d.parsedCurlString = fmt.Sprintf("%s--key '%s' ", d.parsedCurlString, clientKey)
finalCurlString = fmt.Sprintf("%s--key '%s' ", finalCurlString, clientKey)
}
for k, v := range d.Request.Header {
for _, h := range v {
if strings.ToLower(k) == "x-vault-token" {
h = `$(vault print token)`
}
d.parsedCurlString = fmt.Sprintf("%s-H \"%s: %s\" ", d.parsedCurlString, k, h)
finalCurlString = fmt.Sprintf("%s-H \"%s: %s\" ", finalCurlString, k, h)
}
}
@ -78,15 +88,8 @@ func (d *OutputStringError) parseRequest() {
// We need to escape single quotes since that's what we're using to
// quote the body
escapedBody := strings.Replace(string(body), "'", "'\"'\"'", -1)
d.parsedCurlString = fmt.Sprintf("%s-d '%s' ", d.parsedCurlString, escapedBody)
finalCurlString = fmt.Sprintf("%s-d '%s' ", finalCurlString, escapedBody)
}
d.parsedCurlString = fmt.Sprintf("%s%s", d.parsedCurlString, d.Request.URL.String())
}
func (d *OutputStringError) CurlString() string {
if d.parsedCurlString == "" {
d.parseRequest()
}
return d.parsedCurlString
return fmt.Sprintf("%s%s", finalCurlString, d.Request.URL.String()), nil
}

View File

@ -9,6 +9,7 @@ import (
"flag"
"net/url"
"os"
"regexp"
squarejwt "gopkg.in/square/go-jose.v2/jwt"
@ -23,6 +24,49 @@ var (
// PluginUnwrapTokenEnv is the ENV name used to pass unwrap tokens to the
// plugin.
PluginUnwrapTokenEnv = "VAULT_UNWRAP_TOKEN"
// sudoPaths is a map containing the paths that require a token's policy
// to have the "sudo" capability. The keys are the paths as strings, in
// the same format as they are returned by the OpenAPI spec. The values
// are the regular expressions that can be used to test whether a given
// path matches that path or not (useful specifically for the paths that
// contain templated fields.)
sudoPaths = map[string]*regexp.Regexp{
"/auth/token/accessors/": regexp.MustCompile(`^/auth/token/accessors/$`),
"/pki/root": regexp.MustCompile(`^/pki/root$`),
"/pki/root/sign-self-issued": regexp.MustCompile(`^/pki/root/sign-self-issued$`),
"/sys/audit": regexp.MustCompile(`^/sys/audit$`),
"/sys/audit/{path}": regexp.MustCompile(`^/sys/audit/.+$`),
"/sys/auth/{path}": regexp.MustCompile(`^/sys/auth/.+$`),
"/sys/auth/{path}/tune": regexp.MustCompile(`^/sys/auth/.+/tune$`),
"/sys/config/auditing/request-headers": regexp.MustCompile(`^/sys/config/auditing/request-headers$`),
"/sys/config/auditing/request-headers/{header}": regexp.MustCompile(`^/sys/config/auditing/request-headers/.+$`),
"/sys/config/cors": regexp.MustCompile(`^/sys/config/cors$`),
"/sys/config/ui/headers/": regexp.MustCompile(`^/sys/config/ui/headers/$`),
"/sys/config/ui/headers/{header}": regexp.MustCompile(`^/sys/config/ui/headers/.+$`),
"/sys/leases": regexp.MustCompile(`^/sys/leases$`),
"/sys/leases/lookup/": regexp.MustCompile(`^/sys/leases/lookup/$`),
"/sys/leases/lookup/{prefix}": regexp.MustCompile(`^/sys/leases/lookup/.+$`),
"/sys/leases/revoke-force/{prefix}": regexp.MustCompile(`^/sys/leases/revoke-force/.+$`),
"/sys/leases/revoke-prefix/{prefix}": regexp.MustCompile(`^/sys/leases/revoke-prefix/.+$`),
"/sys/plugins/catalog/{name}": regexp.MustCompile(`^/sys/plugins/catalog/[^/]+$`),
"/sys/plugins/catalog/{type}": regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+$`),
"/sys/plugins/catalog/{type}/{name}": regexp.MustCompile(`^/sys/plugins/catalog/[\w-]+/[^/]+$`),
"/sys/raw": regexp.MustCompile(`^/sys/raw$`),
"/sys/raw/{path}": regexp.MustCompile(`^/sys/raw/.+$`),
"/sys/remount": regexp.MustCompile(`^/sys/remount$`),
"/sys/revoke-force/{prefix}": regexp.MustCompile(`^/sys/revoke-force/.+$`),
"/sys/revoke-prefix/{prefix}": regexp.MustCompile(`^/sys/revoke-prefix/.+$`),
"/sys/rotate": regexp.MustCompile(`^/sys/rotate$`),
// enterprise-only paths
"/sys/replication/dr/primary/secondary-token": regexp.MustCompile(`^/sys/replication/dr/primary/secondary-token$`),
"/sys/replication/performance/primary/secondary-token": regexp.MustCompile(`^/sys/replication/performance/primary/secondary-token$`),
"/sys/replication/primary/secondary-token": regexp.MustCompile(`^/sys/replication/primary/secondary-token$`),
"/sys/replication/reindex": regexp.MustCompile(`^/sys/replication/reindex$`),
"/sys/storage/raft/snapshot-auto/config/": regexp.MustCompile(`^/sys/storage/raft/snapshot-auto/config/$`),
"/sys/storage/raft/snapshot-auto/config/{name}": regexp.MustCompile(`^/sys/storage/raft/snapshot-auto/config/[^/]+$`),
}
)
// PluginAPIClientMeta is a helper that plugins can use to configure TLS connections
@ -192,3 +236,28 @@ func VaultPluginTLSProviderContext(ctx context.Context, apiTLSConfig *TLSConfig)
return tlsConfig, nil
}
}
func SudoPaths() map[string]*regexp.Regexp {
return sudoPaths
}
// Determine whether the given path requires the sudo capability
func IsSudoPath(path string) bool {
// Return early if the path is any of the non-templated sudo paths.
if _, ok := sudoPaths[path]; ok {
return true
}
// Some sudo paths have templated fields in them.
// (e.g. /sys/revoke-prefix/{prefix})
// The values in the sudoPaths map are actually regular expressions,
// so we can check if our path matches against them.
for _, sudoPathRegexp := range sudoPaths {
match := sudoPathRegexp.MatchString(path)
if match {
return true
}
}
return false
}

View File

@ -94,12 +94,7 @@ func (s *Secret) TokenRemainingUses() (int, error) {
return -1, nil
}
uses, err := parseutil.ParseInt(s.Data["num_uses"])
if err != nil {
return 0, err
}
return int(uses), nil
return parseutil.SafeParseInt(s.Data["num_uses"])
}
// TokenPolicies returns the standardized list of policies for the given secret.

View File

@ -37,4 +37,7 @@ type HANode struct {
ClusterAddress string `json:"cluster_address"`
ActiveNode bool `json:"active_node"`
LastEcho *time.Time `json:"last_echo"`
Version string `json:"version"`
UpgradeVersion string `json:"upgrade_version,omitempty"`
RedundancyZone string `json:"redundancy_zone,omitempty"`
}

45
vendor/github.com/hashicorp/vault/api/sys_mfa.go generated vendored Normal file
View File

@ -0,0 +1,45 @@
package api
import (
"context"
"fmt"
"net/http"
)
func (c *Sys) MFAValidate(requestID string, payload map[string]interface{}) (*Secret, error) {
return c.MFAValidateWithContext(context.Background(), requestID, payload)
}
func (c *Sys) MFAValidateWithContext(ctx context.Context, requestID string, payload map[string]interface{}) (*Secret, error) {
ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
defer cancelFunc()
body := map[string]interface{}{
"mfa_request_id": requestID,
"mfa_payload": payload,
}
r := c.c.NewRequest(http.MethodPost, fmt.Sprintf("/v1/sys/mfa/validate"))
if err := r.SetJSONBody(body); err != nil {
return nil, fmt.Errorf("failed to set request body: %w", err)
}
resp, err := c.c.rawRequestWithContext(ctx, r)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, err
}
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to parse secret from response: %w", err)
}
if secret == nil {
return nil, fmt.Errorf("data from server response is empty")
}
return secret, nil
}

View File

@ -5,11 +5,13 @@ import (
"context"
"fmt"
"net/http"
"github.com/hashicorp/vault/sdk/helper/logging"
)
// Monitor returns a channel that outputs strings containing the log messages
// coming from the server.
func (c *Sys) Monitor(ctx context.Context, logLevel string) (chan string, error) {
func (c *Sys) Monitor(ctx context.Context, logLevel string, logFormat string) (chan string, error) {
r := c.c.NewRequest(http.MethodGet, "/v1/sys/monitor")
if logLevel == "" {
@ -18,6 +20,12 @@ func (c *Sys) Monitor(ctx context.Context, logLevel string) (chan string, error)
r.Params.Add("log_level", logLevel)
}
if logFormat == "" || logFormat == logging.UnspecifiedFormat.String() {
r.Params.Add("log_format", "standard")
} else {
r.Params.Add("log_format", logFormat)
}
resp, err := c.c.RawRequestWithContext(ctx, r)
if err != nil {
return nil, err

View File

@ -100,23 +100,24 @@ func (c *Sys) ListPluginsWithContext(ctx context.Context, i *ListPluginsInput) (
PluginsByType: make(map[consts.PluginType][]string),
}
if i.Type == consts.PluginTypeUnknown {
for pluginTypeStr, pluginsRaw := range secret.Data {
pluginType, err := consts.ParsePluginType(pluginTypeStr)
if err != nil {
return nil, err
for _, pluginType := range consts.PluginTypes {
pluginsRaw, ok := secret.Data[pluginType.String()]
if !ok {
continue
}
pluginsIfc, ok := pluginsRaw.([]interface{})
if !ok {
return nil, fmt.Errorf("unable to parse plugins for %q type", pluginTypeStr)
return nil, fmt.Errorf("unable to parse plugins for %q type", pluginType.String())
}
plugins := make([]string, len(pluginsIfc))
for i, nameIfc := range pluginsIfc {
plugins := make([]string, 0, len(pluginsIfc))
for _, nameIfc := range pluginsIfc {
name, ok := nameIfc.(string)
if !ok {
continue
}
plugins[i] = name
plugins = append(plugins, name)
}
result.PluginsByType[pluginType] = plugins
}

View File

@ -44,6 +44,7 @@ type AutopilotConfig struct {
MaxTrailingLogs uint64 `json:"max_trailing_logs" mapstructure:"max_trailing_logs"`
MinQuorum uint `json:"min_quorum" mapstructure:"min_quorum"`
ServerStabilizationTime time.Duration `json:"server_stabilization_time" mapstructure:"-"`
DisableUpgradeMigration bool `json:"disable_upgrade_migration" mapstructure:"disable_upgrade_migration"`
}
// MarshalJSON makes the autopilot config fields JSON compatible
@ -55,6 +56,7 @@ func (ac *AutopilotConfig) MarshalJSON() ([]byte, error) {
"max_trailing_logs": ac.MaxTrailingLogs,
"min_quorum": ac.MinQuorum,
"server_stabilization_time": ac.ServerStabilizationTime.String(),
"disable_upgrade_migration": ac.DisableUpgradeMigration,
})
}
@ -90,6 +92,9 @@ type AutopilotState struct {
Leader string `mapstructure:"leader"`
Voters []string `mapstructure:"voters"`
NonVoters []string `mapstructure:"non_voters"`
RedundancyZones map[string]AutopilotZone `mapstructure:"redundancy_zones,omitempty"`
Upgrade *AutopilotUpgrade `mapstructure:"upgrade_info,omitempty"`
OptimisticFailureTolerance int `mapstructure:"optimistic_failure_tolerance,omitempty"`
}
// AutopilotServer represents the server blocks in the response of the raft
@ -105,7 +110,35 @@ type AutopilotServer struct {
Healthy bool `mapstructure:"healthy"`
StableSince string `mapstructure:"stable_since"`
Status string `mapstructure:"status"`
Meta map[string]string `mapstructure:"meta"`
Version string `mapstructure:"version"`
UpgradeVersion string `mapstructure:"upgrade_version,omitempty"`
RedundancyZone string `mapstructure:"redundancy_zone,omitempty"`
NodeType string `mapstructure:"node_type,omitempty"`
}
type AutopilotZone struct {
Servers []string `mapstructure:"servers,omitempty"`
Voters []string `mapstructure:"voters,omitempty"`
FailureTolerance int `mapstructure:"failure_tolerance,omitempty"`
}
type AutopilotUpgrade struct {
Status string `mapstructure:"status"`
TargetVersion string `mapstructure:"target_version,omitempty"`
TargetVersionVoters []string `mapstructure:"target_version_voters,omitempty"`
TargetVersionNonVoters []string `mapstructure:"target_version_non_voters,omitempty"`
TargetVersionReadReplicas []string `mapstructure:"target_version_read_replicas,omitempty"`
OtherVersionVoters []string `mapstructure:"other_version_voters,omitempty"`
OtherVersionNonVoters []string `mapstructure:"other_version_non_voters,omitempty"`
OtherVersionReadReplicas []string `mapstructure:"other_version_read_replicas,omitempty"`
RedundancyZones map[string]AutopilotZoneUpgradeVersions `mapstructure:"redundancy_zones,omitempty"`
}
type AutopilotZoneUpgradeVersions struct {
TargetVersionVoters []string `mapstructure:"target_version_voters,omitempty"`
TargetVersionNonVoters []string `mapstructure:"target_version_non_voters,omitempty"`
OtherVersionVoters []string `mapstructure:"other_version_voters,omitempty"`
OtherVersionNonVoters []string `mapstructure:"other_version_non_voters,omitempty"`
}
// RaftJoin wraps RaftJoinWithContext using context.Background.

View File

@ -101,6 +101,7 @@ type SealStatusResponse struct {
Progress int `json:"progress"`
Nonce string `json:"nonce"`
Version string `json:"version"`
BuildDate string `json:"build_date"`
Migration bool `json:"migration"`
ClusterName string `json:"cluster_name,omitempty"`
ClusterID string `json:"cluster_id,omitempty"`

View File

@ -150,6 +150,46 @@ func ParsePKIJSON(input []byte) (*ParsedCertBundle, error) {
return nil, errutil.UserError{Err: "unable to parse out of either secret data or a secret object"}
}
func ParseDERKey(privateKeyBytes []byte) (signer crypto.Signer, format BlockType, err error) {
if signer, err = x509.ParseECPrivateKey(privateKeyBytes); err == nil {
format = ECBlock
return
}
if signer, err = x509.ParsePKCS1PrivateKey(privateKeyBytes); err == nil {
format = PKCS1Block
return
}
var rawKey interface{}
if rawKey, err = x509.ParsePKCS8PrivateKey(privateKeyBytes); err == nil {
switch rawSigner := rawKey.(type) {
case *rsa.PrivateKey:
signer = rawSigner
case *ecdsa.PrivateKey:
signer = rawSigner
case ed25519.PrivateKey:
signer = rawSigner
default:
return nil, UnknownBlock, errutil.InternalError{Err: "unknown type for parsed PKCS8 Private Key"}
}
format = PKCS8Block
return
}
return nil, UnknownBlock, err
}
func ParsePEMKey(keyPem string) (crypto.Signer, BlockType, error) {
pemBlock, _ := pem.Decode([]byte(keyPem))
if pemBlock == nil {
return nil, UnknownBlock, errutil.UserError{Err: "no data found in PEM block"}
}
return ParseDERKey(pemBlock.Bytes)
}
// ParsePEMBundle takes a string of concatenated PEM-format certificate
// and private key values and decodes/parses them, checking validity along
// the way. The first certificate must be the subject certificate and issuing
@ -170,43 +210,19 @@ func ParsePEMBundle(pemBundle string) (*ParsedCertBundle, error) {
return nil, errutil.UserError{Err: "no data found in PEM block"}
}
if signer, err := x509.ParseECPrivateKey(pemBlock.Bytes); err == nil {
if signer, format, err := ParseDERKey(pemBlock.Bytes); err == nil {
if parsedBundle.PrivateKeyType != UnknownPrivateKey {
return nil, errutil.UserError{Err: "more than one private key given; provide only one private key in the bundle"}
}
parsedBundle.PrivateKeyFormat = ECBlock
parsedBundle.PrivateKeyType = ECPrivateKey
parsedBundle.PrivateKeyBytes = pemBlock.Bytes
parsedBundle.PrivateKey = signer
} else if signer, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes); err == nil {
if parsedBundle.PrivateKeyType != UnknownPrivateKey {
return nil, errutil.UserError{Err: "more than one private key given; provide only one private key in the bundle"}
parsedBundle.PrivateKeyFormat = format
parsedBundle.PrivateKeyType = GetPrivateKeyTypeFromSigner(signer)
if parsedBundle.PrivateKeyType == UnknownPrivateKey {
return nil, errutil.UserError{Err: "Unknown type of private key included in the bundle: %v"}
}
parsedBundle.PrivateKeyType = RSAPrivateKey
parsedBundle.PrivateKeyFormat = PKCS1Block
parsedBundle.PrivateKeyBytes = pemBlock.Bytes
parsedBundle.PrivateKey = signer
} else if signer, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes); err == nil {
parsedBundle.PrivateKeyFormat = PKCS8Block
if parsedBundle.PrivateKeyType != UnknownPrivateKey {
return nil, errutil.UserError{Err: "More than one private key given; provide only one private key in the bundle"}
}
switch signer := signer.(type) {
case *rsa.PrivateKey:
parsedBundle.PrivateKey = signer
parsedBundle.PrivateKeyType = RSAPrivateKey
parsedBundle.PrivateKeyBytes = pemBlock.Bytes
case *ecdsa.PrivateKey:
parsedBundle.PrivateKey = signer
parsedBundle.PrivateKeyType = ECPrivateKey
parsedBundle.PrivateKeyBytes = pemBlock.Bytes
case ed25519.PrivateKey:
parsedBundle.PrivateKey = signer
parsedBundle.PrivateKeyType = Ed25519PrivateKey
parsedBundle.PrivateKeyBytes = pemBlock.Bytes
}
} else if certificates, err := x509.ParseCertificates(pemBlock.Bytes); err == nil {
certPath = append(certPath, &CertBlock{
Certificate: certificates[0],
@ -336,7 +352,21 @@ func generateSerialNumber(randReader io.Reader) (*big.Int, error) {
return serial, nil
}
// ComparePublicKeys compares two public keys and returns true if they match
// ComparePublicKeysAndType compares two public keys and returns true if they match,
// false if their types or contents differ, and an error on unsupported key types.
func ComparePublicKeysAndType(key1Iface, key2Iface crypto.PublicKey) (bool, error) {
equal, err := ComparePublicKeys(key1Iface, key2Iface)
if err != nil {
if strings.Contains(err.Error(), "key types do not match:") {
return false, nil
}
}
return equal, err
}
// ComparePublicKeys compares two public keys and returns true if they match,
// returns an error if public key types are mismatched, or they are an unsupported key type.
func ComparePublicKeys(key1Iface, key2Iface crypto.PublicKey) (bool, error) {
switch key1Iface.(type) {
case *rsa.PublicKey:
@ -585,25 +615,17 @@ func DefaultOrValueKeyBits(keyType string, keyBits int) (int, error) {
// certain internal circumstances.
func DefaultOrValueHashBits(keyType string, keyBits int, hashBits int) (int, error) {
if keyType == "ec" {
// To comply with BSI recommendations Section 4.2 and Mozilla root
// store policy section 5.1.2, enforce that NIST P-curves use a hash
// length corresponding to curve length. Note that ed25519 does not
// the "ec" key type.
expectedHashBits := expectedNISTPCurveHashBits[keyBits]
if expectedHashBits != hashBits && hashBits != 0 {
return hashBits, fmt.Errorf("unsupported signature hash algorithm length (%d) for NIST P-%d", hashBits, keyBits)
} else if hashBits == 0 {
hashBits = expectedHashBits
}
// Enforcement of curve moved to selectSignatureAlgorithmForECDSA. See
// note there about why.
} else if keyType == "rsa" && hashBits == 0 {
// To match previous behavior (and ignoring NIST's recommendations for
// hash size to align with RSA key sizes), default to SHA-2-256.
hashBits = 256
} else if keyType == "ed25519" || keyType == "ed448" {
} else if keyType == "ed25519" || keyType == "ed448" || keyType == "any" {
// No-op; ed25519 and ed448 internally specify their own hash and
// we do not need to select one. Double hashing isn't supported in
// certificate signing and we must
// certificate signing. Additionally, the any key type can't know
// what hash algorithm to use yet, so default to zero.
return 0, nil
}
@ -642,7 +664,7 @@ func ValidateDefaultOrValueKeyTypeSignatureLength(keyType string, keyBits int, h
// Validates that the length of the hash (in bits) used in the signature
// calculation is a known, approved value.
func ValidateSignatureLength(keyType string, hashBits int) error {
if keyType == "ed25519" || keyType == "ed448" {
if keyType == "any" || keyType == "ec" || keyType == "ed25519" || keyType == "ed448" {
// ed25519 and ed448 include built-in hashing and is not externally
// configurable. There are three modes for each of these schemes:
//
@ -654,6 +676,13 @@ func ValidateSignatureLength(keyType string, hashBits int) error {
//
// In all cases, we won't have a hash algorithm to validate here, so
// return nil.
//
// Additionally, when KeyType is any, we can't yet validate the
// signature algorithm size, so it takes the default zero value.
//
// When KeyType is ec, we also can't validate this value as we're
// forcefully ignoring the users' choice and specifying a value based
// on issuer type.
return nil
}
@ -842,16 +871,25 @@ func createCertificate(data *CreationBundle, randReader io.Reader, privateKeyGen
}
if data.SigningBundle != nil {
if len(data.SigningBundle.Certificate.AuthorityKeyId) > 0 &&
!bytes.Equal(data.SigningBundle.Certificate.AuthorityKeyId, data.SigningBundle.Certificate.SubjectKeyId) {
if (len(data.SigningBundle.Certificate.AuthorityKeyId) > 0 &&
!bytes.Equal(data.SigningBundle.Certificate.AuthorityKeyId, data.SigningBundle.Certificate.SubjectKeyId)) ||
data.Params.ForceAppendCaChain {
var chain []*CertBlock
result.CAChain = []*CertBlock{
{
signingChain := data.SigningBundle.CAChain
// Some bundles already include the root included in the chain, so don't include it twice.
if len(signingChain) == 0 || !bytes.Equal(signingChain[0].Bytes, data.SigningBundle.CertificateBytes) {
chain = append(chain, &CertBlock{
Certificate: data.SigningBundle.Certificate,
Bytes: data.SigningBundle.CertificateBytes,
},
})
}
result.CAChain = append(result.CAChain, data.SigningBundle.CAChain...)
if len(signingChain) > 0 {
chain = append(chain, signingChain...)
}
result.CAChain = chain
}
}
@ -859,16 +897,25 @@ func createCertificate(data *CreationBundle, randReader io.Reader, privateKeyGen
}
func selectSignatureAlgorithmForECDSA(pub crypto.PublicKey, signatureBits int) x509.SignatureAlgorithm {
// If signature bits are configured, prefer them to the default choice.
switch signatureBits {
case 256:
return x509.ECDSAWithSHA256
case 384:
return x509.ECDSAWithSHA384
case 512:
return x509.ECDSAWithSHA512
}
// Previously we preferred the user-specified signature bits for ECDSA
// keys. However, this could result in using a longer hash function than
// the underlying NIST P-curve will encode (e.g., a SHA-512 hash with a
// P-256 key). This isn't ideal: the hash is implicitly truncated
// (effectively turning it into SHA-512/256) and we then need to rely
// on the prefix security of the hash. Since both NIST and Mozilla guidance
// suggest instead using the correct hash function, we should prefer that
// over the operator-specified signatureBits.
//
// Lastly, note that pub above needs to be the _signer's_ public key;
// the issue with DefaultOrValueHashBits is that it is called at role
// configuration time, which might _precede_ issuer generation. Thus
// it only has access to the desired key type and not the actual issuer.
// The reference from that function is reproduced below:
//
// > To comply with BSI recommendations Section 4.2 and Mozilla root
// > store policy section 5.1.2, enforce that NIST P-curves use a hash
// > length corresponding to curve length. Note that ed25519 does not
// > implement the "ec" key type.
key, ok := pub.(*ecdsa.PublicKey)
if !ok {
return x509.ECDSAWithSHA256
@ -1120,7 +1167,7 @@ func signCertificate(data *CreationBundle, randReader io.Reader) (*ParsedCertBun
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %s", err)}
}
result.CAChain = data.SigningBundle.GetCAChain()
result.CAChain = data.SigningBundle.GetFullChain()
return result, nil
}
@ -1190,3 +1237,20 @@ func GetPublicKeySize(key crypto.PublicKey) int {
return -1
}
// CreateKeyBundle create a KeyBundle struct object which includes a generated key
// of keyType with keyBits leveraging the randomness from randReader.
func CreateKeyBundle(keyType string, keyBits int, randReader io.Reader) (KeyBundle, error) {
return CreateKeyBundleWithKeyGenerator(keyType, keyBits, randReader, generatePrivateKey)
}
// CreateKeyBundleWithKeyGenerator create a KeyBundle struct object which includes
// a generated key of keyType with keyBits leveraging the randomness from randReader and
// delegates the actual key generation to keyGenerator
func CreateKeyBundleWithKeyGenerator(keyType string, keyBits int, randReader io.Reader, keyGenerator KeyGenerator) (KeyBundle, error) {
result := KeyBundle{}
if err := keyGenerator(keyType, keyBits, &result, randReader); err != nil {
return result, err
}
return result, nil
}

View File

@ -78,6 +78,7 @@ type BlockType string
// Well-known formats
const (
UnknownBlock BlockType = ""
PKCS1Block BlockType = "RSA PRIVATE KEY"
PKCS8Block BlockType = "PRIVATE KEY"
ECBlock BlockType = "EC PRIVATE KEY"
@ -137,6 +138,25 @@ type ParsedCSRBundle struct {
CSR *x509.CertificateRequest
}
type KeyBundle struct {
PrivateKeyType PrivateKeyType
PrivateKeyBytes []byte
PrivateKey crypto.Signer
}
func GetPrivateKeyTypeFromSigner(signer crypto.Signer) PrivateKeyType {
switch signer.(type) {
case *rsa.PrivateKey:
return RSAPrivateKey
case *ecdsa.PrivateKey:
return ECPrivateKey
case ed25519.PrivateKey:
return Ed25519PrivateKey
default:
return UnknownPrivateKey
}
}
// ToPEMBundle converts a string-based certificate bundle
// to a PEM-based string certificate bundle in trust path
// order, leaf certificate first
@ -661,9 +681,32 @@ type URLEntries struct {
OCSPServers []string `json:"ocsp_servers" structs:"ocsp_servers" mapstructure:"ocsp_servers"`
}
type NotAfterBehavior int
const (
ErrNotAfterBehavior NotAfterBehavior = iota
TruncateNotAfterBehavior
PermitNotAfterBehavior
)
var notAfterBehaviorNames = map[NotAfterBehavior]string{
ErrNotAfterBehavior: "err",
TruncateNotAfterBehavior: "truncate",
PermitNotAfterBehavior: "permit",
}
func (n NotAfterBehavior) String() string {
if name, ok := notAfterBehaviorNames[n]; ok && len(name) > 0 {
return name
}
return "unknown"
}
type CAInfoBundle struct {
ParsedCertBundle
URLs *URLEntries
LeafNotAfterBehavior NotAfterBehavior
}
func (b *CAInfoBundle) GetCAChain() []*CertBlock {
@ -675,13 +718,7 @@ func (b *CAInfoBundle) GetCAChain() []*CertBlock {
(len(b.Certificate.AuthorityKeyId) == 0 &&
!bytes.Equal(b.Certificate.RawIssuer, b.Certificate.RawSubject)) {
chain = append(chain, &CertBlock{
Certificate: b.Certificate,
Bytes: b.CertificateBytes,
})
if b.CAChain != nil && len(b.CAChain) > 0 {
chain = append(chain, b.CAChain...)
}
chain = b.GetFullChain()
}
return chain
@ -690,10 +727,14 @@ func (b *CAInfoBundle) GetCAChain() []*CertBlock {
func (b *CAInfoBundle) GetFullChain() []*CertBlock {
var chain []*CertBlock
// Some bundles already include the root included in the chain,
// so don't include it twice.
if len(b.CAChain) == 0 || !bytes.Equal(b.CAChain[0].Bytes, b.CertificateBytes) {
chain = append(chain, &CertBlock{
Certificate: b.Certificate,
Bytes: b.CertificateBytes,
})
}
if len(b.CAChain) > 0 {
chain = append(chain, b.CAChain...)
@ -738,6 +779,7 @@ type CreationParameters struct {
PolicyIdentifiers []string
BasicConstraintsValidForNonCA bool
SignatureBits int
ForceAppendCaChain bool
// Only used when signing a CA cert
UseCSRValues bool
@ -825,3 +867,30 @@ func AddKeyUsages(data *CreationBundle, certTemplate *x509.Certificate) {
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, x509.ExtKeyUsageMicrosoftKernelCodeSigning)
}
}
// SetParsedPrivateKey sets the private key parameters on the bundle
func (p *KeyBundle) SetParsedPrivateKey(privateKey crypto.Signer, privateKeyType PrivateKeyType, privateKeyBytes []byte) {
p.PrivateKey = privateKey
p.PrivateKeyType = privateKeyType
p.PrivateKeyBytes = privateKeyBytes
}
func (p *KeyBundle) ToPrivateKeyPemString() (string, error) {
block := pem.Block{}
if p.PrivateKeyBytes != nil && len(p.PrivateKeyBytes) > 0 {
block.Bytes = p.PrivateKeyBytes
switch p.PrivateKeyType {
case RSAPrivateKey:
block.Type = "RSA PRIVATE KEY"
case ECPrivateKey:
block.Type = "EC PRIVATE KEY"
default:
block.Type = "PRIVATE KEY"
}
privateKeyPemString := strings.TrimSpace(string(pem.EncodeToMemory(&block)))
return privateKeyPemString, nil
}
return "", errutil.InternalError{Err: "No Private Key Bytes to Wrap"}
}

View File

@ -32,4 +32,6 @@ const (
// ReplicationResolverALPN is the negotiated protocol used for
// resolving replicaiton addresses
ReplicationResolverALPN = "replication_resolver_v1"
VaultEnableFilePermissionsCheckEnv = "VAULT_ENABLE_FILE_PERMISSIONS_CHECK"
)

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.19.3
// protoc v3.19.4
// source: sdk/helper/pluginutil/multiplexing.proto
package pluginutil

View File

@ -8,7 +8,8 @@ import (
)
// Auth is the resulting authentication information that is part of
// Response for credential backends.
// Response for credential backends. It's also attached to Request objects and
// defines the authentication used for the request. This value is audit logged.
type Auth struct {
LeaseOptions
@ -101,10 +102,28 @@ type Auth struct {
// Orphan is set if the token does not have a parent
Orphan bool `json:"orphan"`
// PolicyResults is the set of policies that grant the token access to the
// requesting path.
PolicyResults *PolicyResults `json:"policy_results"`
// MFARequirement
MFARequirement *MFARequirement `json:"mfa_requirement"`
// EntityCreated is set to true if an entity is created as part of a login request
EntityCreated bool `json:"entity_created"`
}
func (a *Auth) GoString() string {
return fmt.Sprintf("*%#v", *a)
}
type PolicyResults struct {
Allowed bool `json:"allowed"`
GrantingPolicies []PolicyInfo `json:"granting_policies"`
}
type PolicyInfo struct {
Name string `json:"name"`
NamespaceId string `json:"namespace_id"`
Type string `json:"type"`
}

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.19.3
// protoc v3.19.4
// source: sdk/logical/identity.proto
package logical

View File

@ -40,17 +40,17 @@ type (
type ManagedKeySystemView interface {
// WithManagedKeyByName retrieves an instantiated managed key for consumption by the given function. The
// provided key can only be used within the scope of that function call
WithManagedKeyByName(ctx context.Context, keyName, mountPoint string, f ManagedKeyConsumer) error
WithManagedKeyByName(ctx context.Context, keyName, backendUUID string, f ManagedKeyConsumer) error
// WithManagedKeyByUUID retrieves an instantiated managed key for consumption by the given function. The
// provided key can only be used within the scope of that function call
WithManagedKeyByUUID(ctx context.Context, keyUuid, mountPoint string, f ManagedKeyConsumer) error
WithManagedKeyByUUID(ctx context.Context, keyUuid, backendUUID string, f ManagedKeyConsumer) error
// WithManagedSigningKeyByName retrieves an instantiated managed signing key for consumption by the given function,
// with the same semantics as WithManagedKeyByName
WithManagedSigningKeyByName(ctx context.Context, keyName, mountPoint string, f ManagedSigningKeyConsumer) error
WithManagedSigningKeyByName(ctx context.Context, keyName, backendUUID string, f ManagedSigningKeyConsumer) error
// WithManagedSigningKeyByUUID retrieves an instantiated managed signing key for consumption by the given function,
// with the same semantics as WithManagedKeyByUUID
WithManagedSigningKeyByUUID(ctx context.Context, keyUuid, mountPoint string, f ManagedSigningKeyConsumer) error
WithManagedSigningKeyByUUID(ctx context.Context, keyUuid, backendUUID string, f ManagedSigningKeyConsumer) error
}
type ManagedAsymmetricKey interface {

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.19.3
// protoc v3.19.4
// source: sdk/logical/plugin.proto
package logical
@ -27,6 +27,10 @@ type PluginEnvironment struct {
// VaultVersion is the version of the Vault server
VaultVersion string `protobuf:"bytes,1,opt,name=vault_version,json=vaultVersion,proto3" json:"vault_version,omitempty"`
// VaultVersionPrerelease is the prerelease information of the Vault server
VaultVersionPrerelease string `protobuf:"bytes,2,opt,name=vault_version_prerelease,json=vaultVersionPrerelease,proto3" json:"vault_version_prerelease,omitempty"`
// VaultVersionMetadata is the version metadata of the Vault server
VaultVersionMetadata string `protobuf:"bytes,3,opt,name=vault_version_metadata,json=vaultVersionMetadata,proto3" json:"vault_version_metadata,omitempty"`
}
func (x *PluginEnvironment) Reset() {
@ -68,18 +72,39 @@ func (x *PluginEnvironment) GetVaultVersion() string {
return ""
}
func (x *PluginEnvironment) GetVaultVersionPrerelease() string {
if x != nil {
return x.VaultVersionPrerelease
}
return ""
}
func (x *PluginEnvironment) GetVaultVersionMetadata() string {
if x != nil {
return x.VaultVersionMetadata
}
return ""
}
var File_sdk_logical_plugin_proto protoreflect.FileDescriptor
var file_sdk_logical_plugin_proto_rawDesc = []byte{
0x0a, 0x18, 0x73, 0x64, 0x6b, 0x2f, 0x6c, 0x6f, 0x67, 0x69, 0x63, 0x61, 0x6c, 0x2f, 0x70, 0x6c,
0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6c, 0x6f, 0x67, 0x69,
0x63, 0x61, 0x6c, 0x22, 0x38, 0x0a, 0x11, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x76,
0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x76, 0x61, 0x75, 0x6c,
0x74, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0c, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x28, 0x5a,
0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68,
0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f,
0x6c, 0x6f, 0x67, 0x69, 0x63, 0x61, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x63, 0x61, 0x6c, 0x22, 0xa8, 0x01, 0x0a, 0x11, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x45, 0x6e,
0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x76, 0x61, 0x75,
0x6c, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0c, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x38,
0x0a, 0x18, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f,
0x70, 0x72, 0x65, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x16, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72,
0x65, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x76, 0x61, 0x75, 0x6c,
0x74, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x56,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x28,
0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73,
0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x73, 0x64, 0x6b,
0x2f, 0x6c, 0x6f, 0x67, 0x69, 0x63, 0x61, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

View File

@ -7,4 +7,10 @@ package logical;
message PluginEnvironment {
// VaultVersion is the version of the Vault server
string vault_version = 1;
// VaultVersionPrerelease is the prerelease information of the Vault server
string vault_version_prerelease = 2;
// VaultVersionMetadata is the version metadata of the Vault server
string vault_version_metadata = 3;
}

View File

@ -120,6 +120,8 @@ func RespondErrorCommon(req *Request, resp *Response, err error) (int, error) {
statusCode = http.StatusPreconditionFailed
case errwrap.Contains(err, ErrPathFunctionalityRemoved.Error()):
statusCode = http.StatusNotFound
case errwrap.Contains(err, ErrRelativePath.Error()):
statusCode = http.StatusBadRequest
}
}

View File

@ -11,6 +11,7 @@ type VersionInfo struct {
Version string `json:"version,omitempty"`
VersionPrerelease string `json:"version_prerelease,omitempty"`
VersionMetadata string `json:"version_metadata,omitempty"`
BuildDate string `json:"build_date,omitempty"`
}
func GetVersion() *VersionInfo {
@ -29,6 +30,7 @@ func GetVersion() *VersionInfo {
Version: ver,
VersionPrerelease: rel,
VersionMetadata: md,
BuildDate: BuildDate,
}
}
@ -70,5 +72,9 @@ func (c *VersionInfo) FullVersionNumber(rev bool) string {
fmt.Fprintf(&versionString, " (%s)", c.Revision)
}
if c.BuildDate != "" {
fmt.Fprintf(&versionString, ", built %s", c.BuildDate)
}
return versionString.String()
}

View File

@ -5,10 +5,13 @@ var (
GitCommit string
GitDescribe string
// The compilation date. This will be filled in by the compiler.
BuildDate string
// Whether cgo is enabled or not; set at build time
CgoEnabled bool
Version = "1.10.0"
Version = "1.11.0"
VersionPrerelease = "dev1"
VersionMetadata = ""
)

View File

@ -1,3 +1,20 @@
## 1.5.0
* New option `IgnoreUntaggedFields` to ignore decoding to any fields
without `mapstructure` (or the configured tag name) set [GH-277]
* New option `ErrorUnset` which makes it an error if any fields
in a target struct are not set by the decoding process. [GH-225]
* New function `OrComposeDecodeHookFunc` to help compose decode hooks. [GH-240]
* Decoding to slice from array no longer crashes [GH-265]
* Decode nested struct pointers to map [GH-271]
* Fix issue where `,squash` was ignored if `Squash` option was set. [GH-280]
* Fix issue where fields with `,omitempty` would sometimes decode
into a map with an empty string key [GH-281]
## 1.4.3
* Fix cases where `json.Number` didn't decode properly [GH-261]
## 1.4.2
* Custom name matchers to support any sort of casing, formatting, etc. for

View File

@ -77,6 +77,28 @@ func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
}
}
// OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned.
// If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages.
func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc {
return func(a, b reflect.Value) (interface{}, error) {
var allErrs string
var out interface{}
var err error
for _, f := range ff {
out, err = DecodeHookExec(f, a, b)
if err != nil {
allErrs += err.Error() + "\n"
continue
}
return out, nil
}
return nil, errors.New(allErrs)
}
}
// StringToSliceHookFunc returns a DecodeHookFunc that converts
// string to []string by splitting on the given sep.
func StringToSliceHookFunc(sep string) DecodeHookFunc {

View File

@ -122,7 +122,7 @@
// field value is zero and a numeric type, the field is empty, and it won't
// be encoded into the destination type.
//
// type Source {
// type Source struct {
// Age int `mapstructure:",omitempty"`
// }
//
@ -215,6 +215,12 @@ type DecoderConfig struct {
// (extra keys).
ErrorUnused bool
// If ErrorUnset is true, then it is an error for there to exist
// fields in the result that were not set in the decoding process
// (extra fields). This only applies to decoding to a struct. This
// will affect all nested structs as well.
ErrorUnset bool
// ZeroFields, if set to true, will zero fields before writing them.
// For example, a map will be emptied before decoded values are put in
// it. If this is false, a map will be merged.
@ -259,6 +265,10 @@ type DecoderConfig struct {
// defaults to "mapstructure"
TagName string
// IgnoreUntaggedFields ignores all struct fields without explicit
// TagName, comparable to `mapstructure:"-"` as default behaviour.
IgnoreUntaggedFields bool
// MatchName is the function used to match the map key to the struct
// field name or tag. Defaults to `strings.EqualFold`. This can be used
// to implement case-sensitive tag values, support snake casing, etc.
@ -284,6 +294,11 @@ type Metadata struct {
// Unused is a slice of keys that were found in the raw value but
// weren't decoded since there was no matching field in the result interface
Unused []string
// Unset is a slice of field names that were found in the result interface
// but weren't set in the decoding process since there was no matching value
// in the input
Unset []string
}
// Decode takes an input structure and uses reflection to translate it to
@ -375,6 +390,10 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) {
if config.Metadata.Unused == nil {
config.Metadata.Unused = make([]string, 0)
}
if config.Metadata.Unset == nil {
config.Metadata.Unset = make([]string, 0)
}
}
if config.TagName == "" {
@ -684,16 +703,12 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e
}
case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
jn := data.(json.Number)
i, err := jn.Int64()
i, err := strconv.ParseUint(string(jn), 0, 64)
if err != nil {
return fmt.Errorf(
"error decoding json.Number into %s: %s", name, err)
}
if i < 0 && !d.config.WeaklyTypedInput {
return fmt.Errorf("cannot parse '%s', %d overflows uint",
name, i)
}
val.SetUint(uint64(i))
val.SetUint(i)
default:
return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s', value: '%v'",
@ -910,9 +925,15 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
tagValue := f.Tag.Get(d.config.TagName)
keyName := f.Name
if tagValue == "" && d.config.IgnoreUntaggedFields {
continue
}
// If Squash is set in the config, we squash the field down.
squash := d.config.Squash && v.Kind() == reflect.Struct && f.Anonymous
v = dereferencePtrToStructIfNeeded(v, d.config.TagName)
// Determine the name of the key in the map
if index := strings.Index(tagValue, ","); index != -1 {
if tagValue[:index] == "-" {
@ -924,7 +945,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
}
// If "squash" is specified in the tag, we squash the field down.
squash = !squash && strings.Index(tagValue[index+1:], "squash") != -1
squash = squash || strings.Index(tagValue[index+1:], "squash") != -1
if squash {
// When squashing, the embedded type can be a pointer to a struct.
if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct {
@ -936,7 +957,9 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
return fmt.Errorf("cannot squash non-struct type '%s'", v.Type())
}
}
keyName = tagValue[:index]
if keyNameTagValue := tagValue[:index]; keyNameTagValue != "" {
keyName = keyNameTagValue
}
} else if len(tagValue) > 0 {
if tagValue == "-" {
continue
@ -1092,7 +1115,7 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
}
// If the input value is nil, then don't allocate since empty != nil
if dataVal.IsNil() {
if dataValKind != reflect.Array && dataVal.IsNil() {
return nil
}
@ -1254,6 +1277,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
dataValKeysUnused[dataValKey.Interface()] = struct{}{}
}
targetValKeysUnused := make(map[interface{}]struct{})
errors := make([]string, 0)
// This slice will keep track of all the structs we'll be decoding.
@ -1358,7 +1382,8 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
if !rawMapVal.IsValid() {
// There was no matching key in the map for the value in
// the struct. Just ignore.
// the struct. Remember it for potential errors and metadata.
targetValKeysUnused[fieldName] = struct{}{}
continue
}
}
@ -1418,6 +1443,17 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
errors = appendErrors(errors, err)
}
if d.config.ErrorUnset && len(targetValKeysUnused) > 0 {
keys := make([]string, 0, len(targetValKeysUnused))
for rawKey := range targetValKeysUnused {
keys = append(keys, rawKey.(string))
}
sort.Strings(keys)
err := fmt.Errorf("'%s' has unset fields: %s", name, strings.Join(keys, ", "))
errors = appendErrors(errors, err)
}
if len(errors) > 0 {
return &Error{errors}
}
@ -1432,6 +1468,14 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
d.config.Metadata.Unused = append(d.config.Metadata.Unused, key)
}
for rawKey := range targetValKeysUnused {
key := rawKey.(string)
if name != "" {
key = name + "." + key
}
d.config.Metadata.Unset = append(d.config.Metadata.Unset, key)
}
}
return nil
@ -1469,3 +1513,28 @@ func getKind(val reflect.Value) reflect.Kind {
return kind
}
}
func isStructTypeConvertibleToMap(typ reflect.Type, checkMapstructureTags bool, tagName string) bool {
for i := 0; i < typ.NumField(); i++ {
f := typ.Field(i)
if f.PkgPath == "" && !checkMapstructureTags { // check for unexported fields
return true
}
if checkMapstructureTags && f.Tag.Get(tagName) != "" { // check for mapstructure tags inside
return true
}
}
return false
}
func dereferencePtrToStructIfNeeded(v reflect.Value, tagName string) reflect.Value {
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
return v
}
deref := v.Elem()
derefT := deref.Type()
if isStructTypeConvertibleToMap(derefT, true, tagName) {
return deref
}
return v
}

10
vendor/modules.txt vendored
View File

@ -271,10 +271,10 @@ github.com/hashicorp/go-rootcerts
# github.com/hashicorp/go-secure-stdlib/mlock v0.1.1
## explicit; go 1.16
github.com/hashicorp/go-secure-stdlib/mlock
# github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1
# github.com/hashicorp/go-secure-stdlib/parseutil v0.1.5
## explicit; go 1.16
github.com/hashicorp/go-secure-stdlib/parseutil
# github.com/hashicorp/go-secure-stdlib/strutil v0.1.1
# github.com/hashicorp/go-secure-stdlib/strutil v0.1.2
## explicit; go 1.16
github.com/hashicorp/go-secure-stdlib/strutil
# github.com/hashicorp/go-sockaddr v1.0.2
@ -305,10 +305,10 @@ github.com/hashicorp/hcl/json/token
## explicit; go 1.13
github.com/hashicorp/vault/command/agent/auth
github.com/hashicorp/vault/command/agent/auth/kubernetes
# github.com/hashicorp/vault/api v1.5.0
# github.com/hashicorp/vault/api v1.6.0
## explicit; go 1.13
github.com/hashicorp/vault/api
# github.com/hashicorp/vault/sdk v0.4.1
# github.com/hashicorp/vault/sdk v0.5.0
## explicit; go 1.16
github.com/hashicorp/vault/sdk/helper/certutil
github.com/hashicorp/vault/sdk/helper/compressutil
@ -386,7 +386,7 @@ github.com/mitchellh/go-homedir
# github.com/mitchellh/go-testing-interface v1.0.0
## explicit
github.com/mitchellh/go-testing-interface
# github.com/mitchellh/mapstructure v1.4.2
# github.com/mitchellh/mapstructure v1.5.0
## explicit; go 1.14
github.com/mitchellh/mapstructure
# github.com/mitchellh/reflectwalk v1.0.1