vault/helper/builtinplugins/registry_test.go
hashicorp-copywrite[bot] 0b12cdcfd1
[COMPLIANCE] License changes (#22290)
* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License.

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUS-1.1

* Fix test that expected exact offset on hcl file

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Sarah Thompson <sthompson@hashicorp.com>
Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
2023-08-10 18:14:03 -07:00

335 lines
8.5 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package builtinplugins
import (
"bufio"
"fmt"
"os"
"reflect"
"regexp"
"testing"
credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
dbMysql "github.com/hashicorp/vault/plugins/database/mysql"
"github.com/hashicorp/vault/sdk/helper/consts"
"golang.org/x/exp/slices"
)
// Test_RegistryGet exercises the (registry).Get functionality by comparing
// factory types and ok response.
func Test_RegistryGet(t *testing.T) {
tests := []struct {
name string
builtin string
pluginType consts.PluginType
want BuiltinFactory
wantOk bool
}{
{
name: "non-existent builtin",
builtin: "foo",
pluginType: consts.PluginTypeCredential,
want: nil,
wantOk: false,
},
{
name: "bad plugin type",
builtin: "app-id",
pluginType: 9000,
want: nil,
wantOk: false,
},
{
name: "known builtin lookup",
builtin: "userpass",
pluginType: consts.PluginTypeCredential,
want: toFunc(credUserpass.Factory),
wantOk: true,
},
{
name: "removed builtin lookup",
builtin: "app-id",
pluginType: consts.PluginTypeCredential,
want: nil,
wantOk: true,
},
{
name: "known builtin lookup",
builtin: "mysql-database-plugin",
pluginType: consts.PluginTypeDatabase,
want: dbMysql.New(dbMysql.DefaultUserNameTemplate),
wantOk: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var got BuiltinFactory
got, ok := Registry.Get(tt.builtin, tt.pluginType)
if ok {
if reflect.TypeOf(got) != reflect.TypeOf(tt.want) {
t.Fatalf("got type: %T, want type: %T", got, tt.want)
}
}
if tt.wantOk != ok {
t.Fatalf("error: got %v, want %v", ok, tt.wantOk)
}
})
}
}
// Test_RegistryKeyCounts is a light unit test used to check the builtin
// registry lists for each plugin type and make sure they match in length.
func Test_RegistryKeyCounts(t *testing.T) {
tests := []struct {
name string
pluginType consts.PluginType
want int // use slice length as test condition
wantOk bool
}{
{
name: "bad plugin type",
pluginType: 9001,
want: 0,
},
{
name: "number of auth plugins",
pluginType: consts.PluginTypeCredential,
want: 19,
},
{
name: "number of database plugins",
pluginType: consts.PluginTypeDatabase,
want: 17,
},
{
name: "number of secrets plugins",
pluginType: consts.PluginTypeSecrets,
want: 19,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
keys := Registry.Keys(tt.pluginType)
if len(keys) != tt.want {
t.Fatalf("got size: %d, want size: %d", len(keys), tt.want)
}
})
}
}
// Test_RegistryContains exercises the (registry).Contains functionality.
func Test_RegistryContains(t *testing.T) {
tests := []struct {
name string
builtin string
pluginType consts.PluginType
want bool
}{
{
name: "non-existent builtin",
builtin: "foo",
pluginType: consts.PluginTypeCredential,
want: false,
},
{
name: "bad plugin type",
builtin: "app-id",
pluginType: 9001,
want: false,
},
{
name: "known builtin lookup",
builtin: "approle",
pluginType: consts.PluginTypeCredential,
want: true,
},
{
name: "removed builtin lookup",
builtin: "app-id",
pluginType: consts.PluginTypeCredential,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Registry.Contains(tt.builtin, tt.pluginType)
if got != tt.want {
t.Fatalf("error: got %v, wanted %v", got, tt.want)
}
})
}
}
// Test_RegistryStatus exercises the (registry).Status functionality.
func Test_RegistryStatus(t *testing.T) {
tests := []struct {
name string
builtin string
pluginType consts.PluginType
want consts.DeprecationStatus
wantOk bool
}{
{
name: "non-existent builtin and valid type",
builtin: "foo",
pluginType: consts.PluginTypeCredential,
want: consts.Unknown,
wantOk: false,
},
{
name: "mismatch builtin and plugin type",
builtin: "app-id",
pluginType: consts.PluginTypeSecrets,
want: consts.Unknown,
wantOk: false,
},
{
name: "existing builtin and invalid plugin type",
builtin: "app-id",
pluginType: 9000,
want: consts.Unknown,
wantOk: false,
},
{
name: "supported builtin lookup",
builtin: "approle",
pluginType: consts.PluginTypeCredential,
want: consts.Supported,
wantOk: true,
},
{
name: "deprecated builtin lookup",
builtin: "pcf",
pluginType: consts.PluginTypeCredential,
want: consts.Deprecated,
wantOk: true,
},
{
name: "removed builtin lookup",
builtin: "app-id",
pluginType: consts.PluginTypeCredential,
want: consts.Removed,
wantOk: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, ok := Registry.DeprecationStatus(tt.builtin, tt.pluginType)
if got != tt.want {
t.Fatalf("got %+v, wanted %+v", got, tt.want)
}
if ok != tt.wantOk {
t.Fatalf("got ok: %t, want ok: %t", ok, tt.wantOk)
}
})
}
}
// Test_RegistryMatchesGenOpenapi ensures that the plugins mounted in gen_openapi.sh match registry.go
func Test_RegistryMatchesGenOpenapi(t *testing.T) {
const scriptPath = "../../scripts/gen_openapi.sh"
// parseScript fetches the contents of gen_openapi.sh script & extract the relevant lines
parseScript := func(path string) ([]string, []string, error) {
f, err := os.Open(scriptPath)
if err != nil {
return nil, nil, fmt.Errorf("could not open gen_openapi.sh script: %w", err)
}
defer f.Close()
var (
credentialBackends []string
credentialBackendsRe = regexp.MustCompile(`^vault auth enable (?:-.+ )*(?:"([a-zA-Z]+)"|([a-zA-Z]+))$`)
secretsBackends []string
secretsBackendsRe = regexp.MustCompile(`^vault secrets enable (?:-.+ )*(?:"([a-zA-Z]+)"|([a-zA-Z]+))$`)
)
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
if m := credentialBackendsRe.FindStringSubmatch(line); m != nil {
credentialBackends = append(credentialBackends, m[1])
}
if m := secretsBackendsRe.FindStringSubmatch(line); m != nil {
secretsBackends = append(secretsBackends, m[1])
}
}
if err := scanner.Err(); err != nil {
return nil, nil, fmt.Errorf("error scanning gen_openapi.sh: %v", err)
}
return credentialBackends, secretsBackends, nil
}
// ensureInRegistry ensures that the given plugin is in registry and marked as "supported"
ensureInRegistry := func(t *testing.T, name string, pluginType consts.PluginType) {
t.Helper()
// "database" will not be present in registry, it is represented as
// a list of database plugins instead
if name == "database" && pluginType == consts.PluginTypeSecrets {
return
}
deprecationStatus, ok := Registry.DeprecationStatus(name, pluginType)
if !ok {
t.Fatalf("%q %s backend is missing from registry.go; please remove it from gen_openapi.sh", name, pluginType)
}
if deprecationStatus == consts.Removed {
t.Fatalf("%q %s backend is marked 'removed' in registry.go; please remove it from gen_openapi.sh", name, pluginType)
}
}
// ensureInScript ensures that the given plugin name in in gen_openapi.sh script
ensureInScript := func(t *testing.T, scriptBackends []string, name string) {
t.Helper()
for _, excluded := range []string{
"oidc", // alias for "jwt"
"openldap", // alias for "ldap"
} {
if name == excluded {
return
}
}
if !slices.Contains(scriptBackends, name) {
t.Fatalf("%q backend could not be found in gen_openapi.sh, please add it there", name)
}
}
// test starts here
scriptCredentialBackends, scriptSecretsBackends, err := parseScript(scriptPath)
if err != nil {
t.Fatal(err)
}
for _, name := range scriptCredentialBackends {
ensureInRegistry(t, name, consts.PluginTypeCredential)
}
for _, name := range scriptSecretsBackends {
ensureInRegistry(t, name, consts.PluginTypeSecrets)
}
for name, backend := range Registry.credentialBackends {
if backend.DeprecationStatus == consts.Supported {
ensureInScript(t, scriptCredentialBackends, name)
}
}
for name, backend := range Registry.logicalBackends {
if backend.DeprecationStatus == consts.Supported {
ensureInScript(t, scriptSecretsBackends, name)
}
}
}