feat(credential/vault): Implement UPD vault credential extractor (#1520)

pull/6010/head
Andrew Gaffney 11 months ago committed by irenarindos
parent 34b4028d31
commit ee908ddc23

@ -56,7 +56,7 @@ func TestBaseToUsrPass(t *testing.T) {
want: usrPass{user: "", pass: ""},
},
{
name: "no-match-username-secret",
name: "no-match-password-secret",
given: args{
s: data{
"username": "user",

@ -0,0 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
// Package usernamepassworddomain provides access to the username, password, and domain
// stored in a Vault secret.
package usernamepassworddomain

@ -0,0 +1,161 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package usernamepassworddomain
import (
"strings"
"github.com/mitchellh/pointerstructure"
)
type (
data map[string]any
// extractFunc attempts to extract the username, password, and domain
// from sd using the provided attribute names, using a known
// Vault data response format.
extractFunc func(sd data, usernameAttr, passwordAttr, domainAttr string) (string, string, string)
)
// Extract attempts to extract the values of the username and password
// stored within the provided data using the given attribute names.
//
// Extract does not return partial results, i.e. if one of the attributes
// were extracted but not the other ("", "") will be returned.
func Extract(d data, usernameAttr, passwordAttr, domainAttr string) (string, string, string) {
for _, f := range []extractFunc{
defaultExtract,
kv2Extract,
} {
username, password, domain := f(d, usernameAttr, passwordAttr, domainAttr)
if username != "" && password != "" && domain != "" {
// got valid username and password from secret
return username, password, domain
}
}
return "", "", ""
}
// defaultExtract looks for the usernameAttr, passwordAttr, and domainAttr in the data map
func defaultExtract(sd data, usernameAttr, passwordAttr, domainAttr string) (username string, password string, domain string) {
if sd == nil {
// nothing to do return early
return "", "", ""
}
var u any
switch {
case strings.HasPrefix(usernameAttr, "/"):
var err error
u, err = pointerstructure.Get(sd, usernameAttr)
if err != nil {
return "", "", ""
}
default:
u = sd[usernameAttr]
}
if u, ok := u.(string); ok {
username = u
}
var p any
switch {
case strings.HasPrefix(passwordAttr, "/"):
var err error
p, err = pointerstructure.Get(sd, passwordAttr)
if err != nil {
return "", "", ""
}
default:
p = sd[passwordAttr]
}
if p, ok := p.(string); ok {
password = p
}
var d any
switch {
case strings.HasPrefix(domainAttr, "/"):
var err error
d, err = pointerstructure.Get(sd, domainAttr)
if err != nil {
return "", "", ""
}
default:
d = sd[domainAttr]
}
if d, ok := d.(string); ok {
domain = d
}
return username, password, domain
}
// kv2Extract looks for the usernameAttr, passwordAttr, and domainAttr in the embedded
// 'data' field within the data map.
//
// Additionally it validates the data is in the expected KV-v2 format:
//
// {
// "data": {},
// "metadata: {}
// }
//
// If the format does not match, it returns ("", "", ""). See:
// https://www.vaultproject.io/api/secret/kv/kv-v2#sample-response-1
func kv2Extract(sd data, usernameAttr, passwordAttr, domainAttr string) (username string, password string, domain string) {
if sd == nil {
// nothing to do return early
return "", "", ""
}
var data, metadata map[string]any
for k, v := range sd {
switch k {
case "data":
var ok bool
if data, ok = v.(map[string]any); !ok {
// data field should be of type map[string]interface{} in KV-v2
return "", "", ""
}
case "metadata":
var ok bool
if metadata, ok = v.(map[string]any); !ok {
// metadata field should be of type map[string]interface{} in KV-v2
return "", "", ""
}
default:
// secretData contains a non valid KV-v2 top level field
return "", "", ""
}
}
if data == nil || metadata == nil {
// missing required KV-v2 field
return "", "", ""
}
if u, ok := data[usernameAttr]; ok {
if u, ok := u.(string); ok {
username = u
}
}
if p, ok := data[passwordAttr]; ok {
if p, ok := p.(string); ok {
password = p
}
}
if d, ok := data[domainAttr]; ok {
if d, ok := d.(string); ok {
domain = d
}
}
return username, password, domain
}

@ -0,0 +1,325 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package usernamepassworddomain
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestBaseToUsrPassDomain(t *testing.T) {
t.Parallel()
type args struct {
s data
uAttr string
pAttr string
dAttr string
}
type usrPassDomain struct {
user string
pass string
domain string
}
tests := []struct {
name string
given args
want usrPassDomain
}{
{
name: "nil-input",
want: usrPassDomain{user: "", pass: "", domain: ""},
},
{
name: "no-input",
given: args{},
want: usrPassDomain{user: "", pass: "", domain: ""},
},
{
name: "no-secret",
given: args{
uAttr: "username",
pAttr: "password",
dAttr: "domain",
},
want: usrPassDomain{user: "", pass: "", domain: ""},
},
{
name: "no-match-username-secret",
given: args{
s: data{
"username-wrong": "user",
"password": "pass",
"domain": "domain",
},
uAttr: "username",
pAttr: "password",
dAttr: "domain",
},
want: usrPassDomain{user: "", pass: "", domain: ""},
},
{
name: "no-match-password-secret",
given: args{
s: data{
"username": "user",
"password-wrong": "pass",
"domain": "domain",
},
uAttr: "username",
pAttr: "password",
dAttr: "domain",
},
want: usrPassDomain{user: "", pass: "", domain: ""},
},
{
name: "no-match-domain-secret",
given: args{
s: data{
"username": "user",
"password": "pass",
"domain-wrong": "domain",
},
uAttr: "username",
pAttr: "password",
dAttr: "domain",
},
want: usrPassDomain{user: "", pass: "", domain: ""},
},
{
name: "valid-default",
given: args{
s: data{
"username": "user",
"password": "pass",
"domain": "domain",
},
uAttr: "username",
pAttr: "password",
dAttr: "domain",
},
want: usrPassDomain{user: "user", pass: "pass", domain: "domain"},
},
{
name: "no-match-username-secret-kv2",
given: args{
s: data{
"metadata": map[string]any{},
"data": map[string]any{
"username-wrong": "user",
"password": "pass",
"domain": "domain",
},
},
uAttr: "username",
pAttr: "password",
dAttr: "domain",
},
want: usrPassDomain{user: "", pass: "", domain: ""},
},
{
name: "no-match-password-secret-kv2",
given: args{
s: data{
"metadata": map[string]any{},
"data": map[string]any{
"username": "user",
"password-wrong": "pass",
"domain": "domain",
},
},
uAttr: "username",
pAttr: "password",
dAttr: "domain",
},
want: usrPassDomain{user: "", pass: "", domain: ""},
},
{
name: "no-match-domain-secret-kv2",
given: args{
s: data{
"metadata": map[string]any{},
"data": map[string]any{
"username": "user",
"password": "pass",
"domain-wrong": "domain",
},
},
uAttr: "username",
pAttr: "password",
dAttr: "domain",
},
want: usrPassDomain{user: "", pass: "", domain: ""},
},
{
name: "valid-kv2",
given: args{
s: data{
"metadata": map[string]any{},
"data": map[string]any{
"username": "user",
"password": "pass",
"domain": "domain",
},
},
uAttr: "username",
pAttr: "password",
dAttr: "domain",
},
want: usrPassDomain{user: "user", pass: "pass", domain: "domain"},
},
{
name: "no-metadata-kv2",
given: args{
s: data{
"data": map[string]any{
"username": "user",
"password": "pass",
"domain": "domain",
},
},
uAttr: "username",
pAttr: "password",
dAttr: "domain",
},
want: usrPassDomain{user: "", pass: "", domain: ""},
},
{
name: "invalid-metadata-kv2",
given: args{
s: data{
"metadata": "string",
"data": map[string]any{
"username": "user",
"password": "pass",
"domain": "domain",
},
},
uAttr: "username",
pAttr: "password",
dAttr: "domain",
},
want: usrPassDomain{user: "", pass: "", domain: ""},
},
{
name: "invalid-field-kv2",
given: args{
s: data{
"invalid": map[string]any{},
"metadata": map[string]any{},
"data": map[string]any{
"username": "user",
"password": "pass",
"domain": "domain",
},
},
uAttr: "username",
pAttr: "password",
dAttr: "domain",
},
want: usrPassDomain{user: "", pass: "", domain: ""},
},
{
name: "valid-order-default-first",
given: args{
s: data{
"username": "default-user",
"password": "default-pass",
"domain": "default-domain",
"metadata": map[string]any{},
"data": map[string]any{
"username": "kv2-user",
"password": "kv2-pass",
"domain": "kv2-domain",
},
},
uAttr: "username",
pAttr: "password",
dAttr: "domain",
},
want: usrPassDomain{user: "default-user", pass: "default-pass", domain: "default-domain"},
},
{
name: "default-user-json-pointer-password",
given: args{
s: data{
"username": "default-user",
"domain": "default-domain",
"testing": map[string]any{
"my-password": "secret",
},
},
uAttr: "username",
pAttr: "/testing/my-password",
dAttr: "domain",
},
want: usrPassDomain{user: "default-user", pass: "secret", domain: "default-domain"},
},
{
name: "default-pk-json-pointer-user",
given: args{
s: data{
"password": "default-pass",
"domain": "default-domain",
"testing": map[string]any{
"a-user-name": "me",
},
},
uAttr: "/testing/a-user-name",
pAttr: "password",
dAttr: "domain",
},
want: usrPassDomain{user: "me", pass: "default-pass", domain: "default-domain"},
},
{
name: "default-dm-json-pointer-user",
given: args{
s: data{
"username": "default-user",
"password": "default-pass",
"domain-site": map[string]any{
"a-domain": "domain.com",
},
},
uAttr: "username",
pAttr: "password",
dAttr: "/domain-site/a-domain",
},
want: usrPassDomain{user: "default-user", pass: "default-pass", domain: "domain.com"},
},
{
name: "all-json-pointer",
given: args{
s: data{
"first-path": map[string]any{
"deeper-path": map[string]any{
"my-special-user": "you-found-me",
},
},
"testing": map[string]any{
"password": "secret",
},
"domain-site": map[string]any{
"a-domain": "domain.com",
},
},
uAttr: "/first-path/deeper-path/my-special-user",
pAttr: "/testing/password",
dAttr: "/domain-site/a-domain",
},
want: usrPassDomain{user: "you-found-me", pass: "secret", domain: "domain.com"},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert := assert.New(t)
user, pass, domain := Extract(tt.given.s, tt.given.uAttr, tt.given.pAttr, tt.given.dAttr)
assert.Equal(tt.want.user, user)
assert.Equal(tt.want.pass, pass)
assert.Equal(tt.want.domain, domain)
})
}
}
Loading…
Cancel
Save