| // 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) |
| } |
| }) |
| } |
| } |