744 lines
13 KiB
Go
744 lines
13 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package command
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/go-secure-stdlib/strutil"
|
|
"github.com/hashicorp/vault/api"
|
|
"github.com/posener/complete"
|
|
)
|
|
|
|
func TestPredictVaultPaths(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client, closer := testVaultServer(t)
|
|
defer closer()
|
|
|
|
data := map[string]interface{}{"a": "b"}
|
|
if _, err := client.Logical().Write("secret/bar", data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := client.Logical().Write("secret/foo", data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := client.Logical().Write("secret/zip/zap", data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := client.Logical().Write("secret/zip/zonk", data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := client.Logical().Write("secret/zip/twoot", data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := client.Sys().Mount("level1a/level2a/level3a", &api.MountInput{Type: "kv"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := client.Sys().Mount("level1a/level2a/level3b", &api.MountInput{Type: "kv"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
args complete.Args
|
|
includeFiles bool
|
|
exp []string
|
|
}{
|
|
{
|
|
"has_args",
|
|
complete.Args{
|
|
All: []string{"read", "secret/foo", "a=b"},
|
|
Last: "a=b",
|
|
},
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"has_args_no_files",
|
|
complete.Args{
|
|
All: []string{"read", "secret/foo", "a=b"},
|
|
Last: "a=b",
|
|
},
|
|
false,
|
|
nil,
|
|
},
|
|
{
|
|
"part_mount",
|
|
complete.Args{
|
|
All: []string{"read", "s"},
|
|
Last: "s",
|
|
},
|
|
true,
|
|
[]string{"secret/", "sys/"},
|
|
},
|
|
{
|
|
"part_mount_no_files",
|
|
complete.Args{
|
|
All: []string{"read", "s"},
|
|
Last: "s",
|
|
},
|
|
false,
|
|
[]string{"secret/", "sys/"},
|
|
},
|
|
{
|
|
"only_mount",
|
|
complete.Args{
|
|
All: []string{"read", "sec"},
|
|
Last: "sec",
|
|
},
|
|
true,
|
|
[]string{"secret/bar", "secret/foo", "secret/zip/"},
|
|
},
|
|
{
|
|
"only_mount_no_files",
|
|
complete.Args{
|
|
All: []string{"read", "sec"},
|
|
Last: "sec",
|
|
},
|
|
false,
|
|
[]string{"secret/zip/"},
|
|
},
|
|
{
|
|
"full_mount",
|
|
complete.Args{
|
|
All: []string{"read", "secret"},
|
|
Last: "secret",
|
|
},
|
|
true,
|
|
[]string{"secret/bar", "secret/foo", "secret/zip/"},
|
|
},
|
|
{
|
|
"full_mount_no_files",
|
|
complete.Args{
|
|
All: []string{"read", "secret"},
|
|
Last: "secret",
|
|
},
|
|
false,
|
|
[]string{"secret/zip/"},
|
|
},
|
|
{
|
|
"full_mount_slash",
|
|
complete.Args{
|
|
All: []string{"read", "secret/"},
|
|
Last: "secret/",
|
|
},
|
|
true,
|
|
[]string{"secret/bar", "secret/foo", "secret/zip/"},
|
|
},
|
|
{
|
|
"full_mount_slash_no_files",
|
|
complete.Args{
|
|
All: []string{"read", "secret/"},
|
|
Last: "secret/",
|
|
},
|
|
false,
|
|
[]string{"secret/zip/"},
|
|
},
|
|
{
|
|
"path_partial",
|
|
complete.Args{
|
|
All: []string{"read", "secret/z"},
|
|
Last: "secret/z",
|
|
},
|
|
true,
|
|
[]string{"secret/zip/twoot", "secret/zip/zap", "secret/zip/zonk"},
|
|
},
|
|
{
|
|
"path_partial_no_files",
|
|
complete.Args{
|
|
All: []string{"read", "secret/z"},
|
|
Last: "secret/z",
|
|
},
|
|
false,
|
|
[]string{"secret/zip/"},
|
|
},
|
|
{
|
|
"subpath_partial_z",
|
|
complete.Args{
|
|
All: []string{"read", "secret/zip/z"},
|
|
Last: "secret/zip/z",
|
|
},
|
|
true,
|
|
[]string{"secret/zip/zap", "secret/zip/zonk"},
|
|
},
|
|
{
|
|
"subpath_partial_z_no_files",
|
|
complete.Args{
|
|
All: []string{"read", "secret/zip/z"},
|
|
Last: "secret/zip/z",
|
|
},
|
|
false,
|
|
[]string{"secret/zip/z"},
|
|
},
|
|
{
|
|
"subpath_partial_t",
|
|
complete.Args{
|
|
All: []string{"read", "secret/zip/t"},
|
|
Last: "secret/zip/t",
|
|
},
|
|
true,
|
|
[]string{"secret/zip/twoot"},
|
|
},
|
|
{
|
|
"subpath_partial_t_no_files",
|
|
complete.Args{
|
|
All: []string{"read", "secret/zip/t"},
|
|
Last: "secret/zip/t",
|
|
},
|
|
false,
|
|
[]string{"secret/zip/t"},
|
|
},
|
|
{
|
|
"multi_nested",
|
|
complete.Args{
|
|
All: []string{"read", "level1a/level2a"},
|
|
Last: "level1a/level2a",
|
|
},
|
|
false,
|
|
[]string{
|
|
"level1a/level2a/level3a/",
|
|
"level1a/level2a/level3b/",
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("group", func(t *testing.T) {
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := NewPredict()
|
|
p.client = client
|
|
|
|
f := p.vaultPaths(tc.includeFiles)
|
|
act := f(tc.args)
|
|
if !reflect.DeepEqual(act, tc.exp) {
|
|
t.Errorf("expected %q to be %q", act, tc.exp)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPredict_Audits(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client, closer := testVaultServer(t)
|
|
defer closer()
|
|
|
|
badClient, badCloser := testVaultServerBad(t)
|
|
defer badCloser()
|
|
|
|
if err := client.Sys().EnableAuditWithOptions("file", &api.EnableAuditOptions{
|
|
Type: "file",
|
|
Options: map[string]string{
|
|
"file_path": "discard",
|
|
},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
client *api.Client
|
|
exp []string
|
|
}{
|
|
{
|
|
"not_connected_client",
|
|
badClient,
|
|
nil,
|
|
},
|
|
{
|
|
"good_path",
|
|
client,
|
|
[]string{"file/"},
|
|
},
|
|
}
|
|
|
|
t.Run("group", func(t *testing.T) {
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := NewPredict()
|
|
p.client = tc.client
|
|
|
|
act := p.audits()
|
|
if !reflect.DeepEqual(act, tc.exp) {
|
|
t.Errorf("expected %q to be %q", act, tc.exp)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPredict_Mounts(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client, closer := testVaultServer(t)
|
|
defer closer()
|
|
|
|
badClient, badCloser := testVaultServerBad(t)
|
|
defer badCloser()
|
|
|
|
cases := []struct {
|
|
name string
|
|
client *api.Client
|
|
exp []string
|
|
}{
|
|
{
|
|
"not_connected_client",
|
|
badClient,
|
|
defaultPredictVaultMounts,
|
|
},
|
|
{
|
|
"good_path",
|
|
client,
|
|
[]string{"cubbyhole/", "identity/", "secret/", "sys/"},
|
|
},
|
|
}
|
|
|
|
t.Run("group", func(t *testing.T) {
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := NewPredict()
|
|
p.client = tc.client
|
|
|
|
act := p.mounts()
|
|
if !reflect.DeepEqual(act, tc.exp) {
|
|
t.Errorf("expected %q to be %q", act, tc.exp)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPredict_Plugins(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client, closer := testVaultServer(t)
|
|
defer closer()
|
|
|
|
badClient, badCloser := testVaultServerBad(t)
|
|
defer badCloser()
|
|
|
|
cases := []struct {
|
|
name string
|
|
client *api.Client
|
|
exp []string
|
|
}{
|
|
{
|
|
"not_connected_client",
|
|
badClient,
|
|
nil,
|
|
},
|
|
{
|
|
"good_path",
|
|
client,
|
|
[]string{
|
|
"ad",
|
|
"alicloud",
|
|
"approle",
|
|
"aws",
|
|
"azure",
|
|
"cassandra-database-plugin",
|
|
"centrify",
|
|
"cert",
|
|
"cf",
|
|
"consul",
|
|
"couchbase-database-plugin",
|
|
"elasticsearch-database-plugin",
|
|
"gcp",
|
|
"gcpkms",
|
|
"github",
|
|
"hana-database-plugin",
|
|
"influxdb-database-plugin",
|
|
"jwt",
|
|
"kerberos",
|
|
"keymgmt",
|
|
"kmip",
|
|
"kubernetes",
|
|
"kv",
|
|
"ldap",
|
|
"mongodb-database-plugin",
|
|
"mongodbatlas",
|
|
"mongodbatlas-database-plugin",
|
|
"mssql-database-plugin",
|
|
"mysql-aurora-database-plugin",
|
|
"mysql-database-plugin",
|
|
"mysql-legacy-database-plugin",
|
|
"mysql-rds-database-plugin",
|
|
"nomad",
|
|
"oci",
|
|
"oidc",
|
|
"okta",
|
|
"openldap",
|
|
"pcf", // Deprecated.
|
|
"pki",
|
|
"postgresql-database-plugin",
|
|
"rabbitmq",
|
|
"radius",
|
|
"redis-database-plugin",
|
|
"redis-elasticache-database-plugin",
|
|
"redshift-database-plugin",
|
|
"snowflake-database-plugin",
|
|
"ssh",
|
|
"terraform",
|
|
"totp",
|
|
"transform",
|
|
"transit",
|
|
"userpass",
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("group", func(t *testing.T) {
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := NewPredict()
|
|
p.client = tc.client
|
|
|
|
act := p.plugins()
|
|
|
|
if !strutil.StrListContains(act, "keymgmt") {
|
|
for i, v := range tc.exp {
|
|
if v == "keymgmt" {
|
|
tc.exp = append(tc.exp[:i], tc.exp[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !strutil.StrListContains(act, "kmip") {
|
|
for i, v := range tc.exp {
|
|
if v == "kmip" {
|
|
tc.exp = append(tc.exp[:i], tc.exp[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !strutil.StrListContains(act, "transform") {
|
|
for i, v := range tc.exp {
|
|
if v == "transform" {
|
|
tc.exp = append(tc.exp[:i], tc.exp[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(act, tc.exp) {
|
|
t.Errorf("expected: %q, got: %q, diff: %v", tc.exp, act, strutil.Difference(act, tc.exp, true))
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPredict_Policies(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client, closer := testVaultServer(t)
|
|
defer closer()
|
|
|
|
badClient, badCloser := testVaultServerBad(t)
|
|
defer badCloser()
|
|
|
|
cases := []struct {
|
|
name string
|
|
client *api.Client
|
|
exp []string
|
|
}{
|
|
{
|
|
"not_connected_client",
|
|
badClient,
|
|
nil,
|
|
},
|
|
{
|
|
"good_path",
|
|
client,
|
|
[]string{"default", "root"},
|
|
},
|
|
}
|
|
|
|
t.Run("group", func(t *testing.T) {
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := NewPredict()
|
|
p.client = tc.client
|
|
|
|
act := p.policies()
|
|
if !reflect.DeepEqual(act, tc.exp) {
|
|
t.Errorf("expected %q to be %q", act, tc.exp)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPredict_Paths(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client, closer := testVaultServer(t)
|
|
defer closer()
|
|
|
|
data := map[string]interface{}{"a": "b"}
|
|
if _, err := client.Logical().Write("secret/bar", data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := client.Logical().Write("secret/foo", data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := client.Logical().Write("secret/zip/zap", data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
path string
|
|
includeFiles bool
|
|
exp []string
|
|
}{
|
|
{
|
|
"bad_path",
|
|
"nope/not/a/real/path/ever",
|
|
true,
|
|
[]string{"nope/not/a/real/path/ever"},
|
|
},
|
|
{
|
|
"good_path",
|
|
"secret/",
|
|
true,
|
|
[]string{"secret/bar", "secret/foo", "secret/zip/"},
|
|
},
|
|
{
|
|
"good_path_no_files",
|
|
"secret/",
|
|
false,
|
|
[]string{"secret/zip/"},
|
|
},
|
|
{
|
|
"partial_match",
|
|
"secret/z",
|
|
true,
|
|
[]string{"secret/zip/"},
|
|
},
|
|
{
|
|
"partial_match_no_files",
|
|
"secret/z",
|
|
false,
|
|
[]string{"secret/zip/"},
|
|
},
|
|
}
|
|
|
|
t.Run("group", func(t *testing.T) {
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := NewPredict()
|
|
p.client = client
|
|
|
|
act := p.paths("kv", "1", tc.path, tc.includeFiles)
|
|
if !reflect.DeepEqual(act, tc.exp) {
|
|
t.Errorf("expected %q to be %q", act, tc.exp)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPredict_PathsKVv2(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client, closer := testVaultServerWithKVVersion(t, "2")
|
|
defer closer()
|
|
|
|
data := map[string]interface{}{"data": map[string]interface{}{"a": "b"}}
|
|
if _, err := client.Logical().Write("secret/data/bar", data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := client.Logical().Write("secret/data/foo", data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := client.Logical().Write("secret/data/zip/zap", data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
path string
|
|
includeFiles bool
|
|
exp []string
|
|
}{
|
|
{
|
|
"bad_path",
|
|
"nope/not/a/real/path/ever",
|
|
true,
|
|
[]string{"nope/not/a/real/path/ever"},
|
|
},
|
|
{
|
|
"good_path",
|
|
"secret/",
|
|
true,
|
|
[]string{"secret/bar", "secret/foo", "secret/zip/"},
|
|
},
|
|
{
|
|
"good_path_no_files",
|
|
"secret/",
|
|
false,
|
|
[]string{"secret/zip/"},
|
|
},
|
|
{
|
|
"partial_match",
|
|
"secret/z",
|
|
true,
|
|
[]string{"secret/zip/"},
|
|
},
|
|
{
|
|
"partial_match_no_files",
|
|
"secret/z",
|
|
false,
|
|
[]string{"secret/zip/"},
|
|
},
|
|
}
|
|
|
|
t.Run("group", func(t *testing.T) {
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := NewPredict()
|
|
p.client = client
|
|
|
|
act := p.paths("kv", "2", tc.path, tc.includeFiles)
|
|
if !reflect.DeepEqual(act, tc.exp) {
|
|
t.Errorf("expected %q to be %q", act, tc.exp)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPredict_ListPaths(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client, closer := testVaultServer(t)
|
|
defer closer()
|
|
|
|
badClient, badCloser := testVaultServerBad(t)
|
|
defer badCloser()
|
|
|
|
data := map[string]interface{}{"a": "b"}
|
|
if _, err := client.Logical().Write("secret/bar", data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := client.Logical().Write("secret/foo", data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
client *api.Client
|
|
path string
|
|
exp []string
|
|
}{
|
|
{
|
|
"bad_path",
|
|
client,
|
|
"nope/not/a/real/path/ever",
|
|
nil,
|
|
},
|
|
{
|
|
"good_path",
|
|
client,
|
|
"secret/",
|
|
[]string{"bar", "foo"},
|
|
},
|
|
{
|
|
"not_connected_client",
|
|
badClient,
|
|
"secret/",
|
|
nil,
|
|
},
|
|
}
|
|
|
|
t.Run("group", func(t *testing.T) {
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := NewPredict()
|
|
p.client = tc.client
|
|
|
|
act := p.listPaths(tc.path)
|
|
if !reflect.DeepEqual(act, tc.exp) {
|
|
t.Errorf("expected %q to be %q", act, tc.exp)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPredict_HasPathArg(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cases := []struct {
|
|
name string
|
|
args []string
|
|
exp bool
|
|
}{
|
|
{
|
|
"nil",
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
"empty",
|
|
[]string{},
|
|
false,
|
|
},
|
|
{
|
|
"empty_string",
|
|
[]string{""},
|
|
false,
|
|
},
|
|
{
|
|
"single",
|
|
[]string{"foo"},
|
|
false,
|
|
},
|
|
{
|
|
"multiple",
|
|
[]string{"foo", "bar", "baz"},
|
|
true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := NewPredict()
|
|
if act := p.hasPathArg(tc.args); act != tc.exp {
|
|
t.Errorf("expected %t to be %t", act, tc.exp)
|
|
}
|
|
})
|
|
}
|
|
}
|