open-vault/vault/logical_system_test.go
Michael Golowka f77bcc53c4
Move sdk/helper/random -> helper/random (#9226)
* This package is new for 1.5 so this is not a breaking change.
* This is being moved because this code was originally intended to be used
within plugins, however the design of password policies has changed such
that this is no longer needed. Thus, this code doesn't need to be in the
public SDK.
2020-06-17 14:24:38 -06:00

3508 lines
101 KiB
Go

package vault
import (
"context"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"time"
"github.com/fatih/structs"
"github.com/go-test/deep"
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/helper/builtinplugins"
"github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/helper/random"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/helper/salt"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/version"
"github.com/mitchellh/mapstructure"
)
func TestSystemBackend_RootPaths(t *testing.T) {
expected := []string{
"auth/*",
"remount",
"audit",
"audit/*",
"raw",
"raw/*",
"replication/primary/secondary-token",
"replication/performance/primary/secondary-token",
"replication/dr/primary/secondary-token",
"replication/reindex",
"replication/dr/reindex",
"replication/performance/reindex",
"rotate",
"config/cors",
"config/auditing/*",
"config/ui/headers/*",
"plugins/catalog/*",
"revoke-prefix/*",
"revoke-force/*",
"leases/revoke-prefix/*",
"leases/revoke-force/*",
"leases/lookup/*",
}
b := testSystemBackend(t)
actual := b.SpecialPaths().Root
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: mismatch\nexpected:\n%#v\ngot:\n%#v", expected, actual)
}
}
func TestSystemConfigCORS(t *testing.T) {
b := testSystemBackend(t)
_, barrier, _ := mockBarrier(t)
view := NewBarrierView(barrier, "")
b.(*SystemBackend).Core.systemBarrierView = view
req := logical.TestRequest(t, logical.UpdateOperation, "config/cors")
req.Data["allowed_origins"] = "http://www.example.com"
req.Data["allowed_headers"] = "X-Custom-Header"
_, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatal(err)
}
expected := &logical.Response{
Data: map[string]interface{}{
"enabled": true,
"allowed_origins": []string{"http://www.example.com"},
"allowed_headers": append(StdAllowedHeaders, "X-Custom-Header"),
},
}
req = logical.TestRequest(t, logical.ReadOperation, "config/cors")
actual, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
// Do it again. Bug #6182
req = logical.TestRequest(t, logical.UpdateOperation, "config/cors")
req.Data["allowed_origins"] = "http://www.example.com"
req.Data["allowed_headers"] = "X-Custom-Header"
_, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatal(err)
}
req = logical.TestRequest(t, logical.ReadOperation, "config/cors")
actual, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
req = logical.TestRequest(t, logical.DeleteOperation, "config/cors")
_, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
req = logical.TestRequest(t, logical.ReadOperation, "config/cors")
actual, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
expected = &logical.Response{
Data: map[string]interface{}{
"enabled": false,
},
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("DELETE FAILED -- bad: %#v", actual)
}
}
func TestSystemBackend_mounts(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.ReadOperation, "mounts")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
// We can't know the pointer address ahead of time so simply
// copy what's given
exp := map[string]interface{}{
"secret/": map[string]interface{}{
"type": "kv",
"external_entropy_access": false,
"description": "key/value secret storage",
"accessor": resp.Data["secret/"].(map[string]interface{})["accessor"],
"uuid": resp.Data["secret/"].(map[string]interface{})["uuid"],
"config": map[string]interface{}{
"default_lease_ttl": resp.Data["secret/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
"max_lease_ttl": resp.Data["secret/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
"force_no_cache": false,
},
"local": false,
"seal_wrap": false,
"options": map[string]string{
"version": "1",
},
},
"sys/": map[string]interface{}{
"type": "system",
"external_entropy_access": false,
"description": "system endpoints used for control, policy and debugging",
"accessor": resp.Data["sys/"].(map[string]interface{})["accessor"],
"uuid": resp.Data["sys/"].(map[string]interface{})["uuid"],
"config": map[string]interface{}{
"default_lease_ttl": resp.Data["sys/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
"max_lease_ttl": resp.Data["sys/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
"force_no_cache": false,
"passthrough_request_headers": []string{"Accept"},
},
"local": false,
"seal_wrap": false,
"options": map[string]string(nil),
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
"type": "cubbyhole",
"external_entropy_access": false,
"accessor": resp.Data["cubbyhole/"].(map[string]interface{})["accessor"],
"uuid": resp.Data["cubbyhole/"].(map[string]interface{})["uuid"],
"config": map[string]interface{}{
"default_lease_ttl": resp.Data["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
"max_lease_ttl": resp.Data["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
"force_no_cache": false,
},
"local": true,
"seal_wrap": false,
"options": map[string]string(nil),
},
"identity/": map[string]interface{}{
"description": "identity store",
"type": "identity",
"external_entropy_access": false,
"accessor": resp.Data["identity/"].(map[string]interface{})["accessor"],
"uuid": resp.Data["identity/"].(map[string]interface{})["uuid"],
"config": map[string]interface{}{
"default_lease_ttl": resp.Data["identity/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
"max_lease_ttl": resp.Data["identity/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
"force_no_cache": false,
},
"local": false,
"seal_wrap": false,
"options": map[string]string(nil),
},
}
if diff := deep.Equal(resp.Data, exp); len(diff) > 0 {
t.Fatalf("bad, diff: %#v", diff)
}
}
func TestSystemBackend_mount(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.UpdateOperation, "mounts/prod/secret/")
req.Data["type"] = "kv"
req.Data["config"] = map[string]interface{}{
"default_lease_ttl": "35m",
"max_lease_ttl": "45m",
}
req.Data["local"] = true
req.Data["seal_wrap"] = true
req.Data["options"] = map[string]string{
"version": "1",
}
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
req = logical.TestRequest(t, logical.ReadOperation, "mounts")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
// We can't know the pointer address ahead of time so simply
// copy what's given
exp := map[string]interface{}{
"secret/": map[string]interface{}{
"type": "kv",
"external_entropy_access": false,
"description": "key/value secret storage",
"accessor": resp.Data["secret/"].(map[string]interface{})["accessor"],
"uuid": resp.Data["secret/"].(map[string]interface{})["uuid"],
"config": map[string]interface{}{
"default_lease_ttl": resp.Data["secret/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
"max_lease_ttl": resp.Data["secret/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
"force_no_cache": false,
},
"local": false,
"seal_wrap": false,
"options": map[string]string{
"version": "1",
},
},
"sys/": map[string]interface{}{
"type": "system",
"external_entropy_access": false,
"description": "system endpoints used for control, policy and debugging",
"accessor": resp.Data["sys/"].(map[string]interface{})["accessor"],
"uuid": resp.Data["sys/"].(map[string]interface{})["uuid"],
"config": map[string]interface{}{
"default_lease_ttl": resp.Data["sys/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
"max_lease_ttl": resp.Data["sys/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
"force_no_cache": false,
"passthrough_request_headers": []string{"Accept"},
},
"local": false,
"seal_wrap": false,
"options": map[string]string(nil),
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
"type": "cubbyhole",
"external_entropy_access": false,
"accessor": resp.Data["cubbyhole/"].(map[string]interface{})["accessor"],
"uuid": resp.Data["cubbyhole/"].(map[string]interface{})["uuid"],
"config": map[string]interface{}{
"default_lease_ttl": resp.Data["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
"max_lease_ttl": resp.Data["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
"force_no_cache": false,
},
"local": true,
"seal_wrap": false,
"options": map[string]string(nil),
},
"identity/": map[string]interface{}{
"description": "identity store",
"type": "identity",
"external_entropy_access": false,
"accessor": resp.Data["identity/"].(map[string]interface{})["accessor"],
"uuid": resp.Data["identity/"].(map[string]interface{})["uuid"],
"config": map[string]interface{}{
"default_lease_ttl": resp.Data["identity/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
"max_lease_ttl": resp.Data["identity/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
"force_no_cache": false,
},
"local": false,
"seal_wrap": false,
"options": map[string]string(nil),
},
"prod/secret/": map[string]interface{}{
"description": "",
"type": "kv",
"external_entropy_access": false,
"accessor": resp.Data["prod/secret/"].(map[string]interface{})["accessor"],
"uuid": resp.Data["prod/secret/"].(map[string]interface{})["uuid"],
"config": map[string]interface{}{
"default_lease_ttl": int64(2100),
"max_lease_ttl": int64(2700),
"force_no_cache": false,
},
"local": true,
"seal_wrap": true,
"options": map[string]string{
"version": "1",
},
},
}
if diff := deep.Equal(resp.Data, exp); len(diff) > 0 {
t.Fatalf("bad: diff: %#v", diff)
}
}
func TestSystemBackend_mount_force_no_cache(t *testing.T) {
core, b, _ := testCoreSystemBackend(t)
req := logical.TestRequest(t, logical.UpdateOperation, "mounts/prod/secret/")
req.Data["type"] = "kv"
req.Data["config"] = map[string]interface{}{
"force_no_cache": true,
}
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
mountEntry := core.router.MatchingMountEntry(namespace.RootContext(nil), "prod/secret/")
if mountEntry == nil {
t.Fatalf("missing mount entry")
}
if !mountEntry.Config.ForceNoCache {
t.Fatalf("bad config %#v", mountEntry)
}
}
func TestSystemBackend_mount_invalid(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.UpdateOperation, "mounts/prod/secret/")
req.Data["type"] = "nope"
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if resp.Data["error"] != `plugin not found in the catalog: nope` {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_unmount(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.DeleteOperation, "mounts/secret/")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
}
var capabilitiesPolicy = `
name = "test"
path "foo/bar*" {
capabilities = ["create", "sudo", "update"]
}
path "sys/capabilities*" {
capabilities = ["update"]
}
path "bar/baz" {
capabilities = ["read", "update"]
}
path "bar/baz" {
capabilities = ["delete"]
}
`
func TestSystemBackend_PathCapabilities(t *testing.T) {
var resp *logical.Response
var err error
core, b, rootToken := testCoreSystemBackend(t)
policy, _ := ParseACLPolicy(namespace.RootNamespace, capabilitiesPolicy)
err = core.policyStore.SetPolicy(namespace.RootContext(nil), policy)
if err != nil {
t.Fatalf("err: %v", err)
}
path1 := "foo/bar"
path2 := "foo/bar/sample"
path3 := "sys/capabilities"
path4 := "bar/baz"
rootCheckFunc := func(t *testing.T, resp *logical.Response) {
// All the paths should have "root" as the capability
expectedRoot := []string{"root"}
if !reflect.DeepEqual(resp.Data[path1], expectedRoot) ||
!reflect.DeepEqual(resp.Data[path2], expectedRoot) ||
!reflect.DeepEqual(resp.Data[path3], expectedRoot) ||
!reflect.DeepEqual(resp.Data[path4], expectedRoot) {
t.Fatalf("bad: capabilities; expected: %#v, actual: %#v", expectedRoot, resp.Data)
}
}
// Check the capabilities using the root token
resp, err = b.HandleRequest(namespace.RootContext(nil), &logical.Request{
Path: "capabilities",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"paths": []string{path1, path2, path3, path4},
"token": rootToken,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
rootCheckFunc(t, resp)
// Check the capabilities using capabilities-self
resp, err = b.HandleRequest(namespace.RootContext(nil), &logical.Request{
ClientToken: rootToken,
Path: "capabilities-self",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"paths": []string{path1, path2, path3, path4},
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
rootCheckFunc(t, resp)
// Lookup the accessor of the root token
te, err := core.tokenStore.Lookup(namespace.RootContext(nil), rootToken)
if err != nil {
t.Fatal(err)
}
// Check the capabilities using capabilities-accessor endpoint
resp, err = b.HandleRequest(namespace.RootContext(nil), &logical.Request{
Path: "capabilities-accessor",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"paths": []string{path1, path2, path3, path4},
"accessor": te.Accessor,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
rootCheckFunc(t, resp)
// Create a non-root token
testMakeServiceTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"test"})
nonRootCheckFunc := func(t *testing.T, resp *logical.Response) {
expected1 := []string{"create", "sudo", "update"}
expected2 := expected1
expected3 := []string{"update"}
expected4 := []string{"delete", "read", "update"}
if !reflect.DeepEqual(resp.Data[path1], expected1) ||
!reflect.DeepEqual(resp.Data[path2], expected2) ||
!reflect.DeepEqual(resp.Data[path3], expected3) ||
!reflect.DeepEqual(resp.Data[path4], expected4) {
t.Fatalf("bad: capabilities; actual: %#v", resp.Data)
}
}
// Check the capabilities using a non-root token
resp, err = b.HandleRequest(namespace.RootContext(nil), &logical.Request{
Path: "capabilities",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"paths": []string{path1, path2, path3, path4},
"token": "tokenid",
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
nonRootCheckFunc(t, resp)
// Check the capabilities of a non-root token using capabilities-self
// endpoint
resp, err = b.HandleRequest(namespace.RootContext(nil), &logical.Request{
ClientToken: "tokenid",
Path: "capabilities-self",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"paths": []string{path1, path2, path3, path4},
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
nonRootCheckFunc(t, resp)
// Lookup the accessor of the non-root token
te, err = core.tokenStore.Lookup(namespace.RootContext(nil), "tokenid")
if err != nil {
t.Fatal(err)
}
// Check the capabilities using a non-root token using
// capabilities-accessor endpoint
resp, err = b.HandleRequest(namespace.RootContext(nil), &logical.Request{
Path: "capabilities-accessor",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"paths": []string{path1, path2, path3, path4},
"accessor": te.Accessor,
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
nonRootCheckFunc(t, resp)
}
func TestSystemBackend_Capabilities_BC(t *testing.T) {
testCapabilities(t, "capabilities")
testCapabilities(t, "capabilities-self")
}
func testCapabilities(t *testing.T, endpoint string) {
core, b, rootToken := testCoreSystemBackend(t)
req := logical.TestRequest(t, logical.UpdateOperation, endpoint)
if endpoint == "capabilities-self" {
req.ClientToken = rootToken
} else {
req.Data["token"] = rootToken
}
req.Data["path"] = "any_path"
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatalf("bad: %v", resp)
}
actual := resp.Data["capabilities"]
expected := []string{"root"}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
}
policy, _ := ParseACLPolicy(namespace.RootNamespace, capabilitiesPolicy)
err = core.policyStore.SetPolicy(namespace.RootContext(nil), policy)
if err != nil {
t.Fatalf("err: %v", err)
}
testMakeServiceTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"test"})
req = logical.TestRequest(t, logical.UpdateOperation, endpoint)
if endpoint == "capabilities-self" {
req.ClientToken = "tokenid"
} else {
req.Data["token"] = "tokenid"
}
req.Data["path"] = "foo/bar"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil {
t.Fatalf("bad: %v", resp)
}
actual = resp.Data["capabilities"]
expected = []string{"create", "sudo", "update"}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
}
}
func TestSystemBackend_CapabilitiesAccessor_BC(t *testing.T) {
core, b, rootToken := testCoreSystemBackend(t)
te, err := core.tokenStore.Lookup(namespace.RootContext(nil), rootToken)
if err != nil {
t.Fatal(err)
}
req := logical.TestRequest(t, logical.UpdateOperation, "capabilities-accessor")
// Accessor of root token
req.Data["accessor"] = te.Accessor
req.Data["path"] = "any_path"
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil {
t.Fatalf("bad: %v", resp)
}
actual := resp.Data["capabilities"]
expected := []string{"root"}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
}
policy, _ := ParseACLPolicy(namespace.RootNamespace, capabilitiesPolicy)
err = core.policyStore.SetPolicy(namespace.RootContext(nil), policy)
if err != nil {
t.Fatalf("err: %v", err)
}
testMakeServiceTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"test"})
te, err = core.tokenStore.Lookup(namespace.RootContext(nil), "tokenid")
if err != nil {
t.Fatal(err)
}
req = logical.TestRequest(t, logical.UpdateOperation, "capabilities-accessor")
req.Data["accessor"] = te.Accessor
req.Data["path"] = "foo/bar"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil {
t.Fatalf("bad: %v", resp)
}
actual = resp.Data["capabilities"]
expected = []string{"create", "sudo", "update"}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
}
}
func TestSystemBackend_remount(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.UpdateOperation, "remount")
req.Data["from"] = "secret"
req.Data["to"] = "foo"
req.Data["config"] = structs.Map(MountConfig{})
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_remount_invalid(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.UpdateOperation, "remount")
req.Data["from"] = "unknown"
req.Data["to"] = "foo"
req.Data["config"] = structs.Map(MountConfig{})
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if resp.Data["error"] != `no matching mount at "unknown/"` {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_remount_system(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.UpdateOperation, "remount")
req.Data["from"] = "sys"
req.Data["to"] = "foo"
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if resp.Data["error"] != `cannot remount "sys/"` {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_leases(t *testing.T) {
core, b, root := testCoreSystemBackend(t)
// Create a key with a lease
req := logical.TestRequest(t, logical.UpdateOperation, "secret/foo")
req.Data["foo"] = "bar"
req.ClientToken = root
resp, err := core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %#v", resp)
}
// Read a key with a LeaseID
req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
req.ClientToken = root
req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}})
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
t.Fatalf("bad: %#v", resp)
}
// Read lease
req = logical.TestRequest(t, logical.UpdateOperation, "leases/lookup")
req.Data["lease_id"] = resp.Secret.LeaseID
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp.Data["renewable"] == nil || resp.Data["renewable"].(bool) {
t.Fatal("kv leases are not renewable")
}
// Invalid lease
req = logical.TestRequest(t, logical.UpdateOperation, "leases/lookup")
req.Data["lease_id"] = "invalid"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("expected invalid request, got err: %v", err)
}
}
func TestSystemBackend_leases_list(t *testing.T) {
core, b, root := testCoreSystemBackend(t)
// Create a key with a lease
req := logical.TestRequest(t, logical.UpdateOperation, "secret/foo")
req.Data["foo"] = "bar"
req.ClientToken = root
resp, err := core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %#v", resp)
}
// Read a key with a LeaseID
req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
req.ClientToken = root
req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}})
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
t.Fatalf("bad: %#v", resp)
}
// List top level
req = logical.TestRequest(t, logical.ListOperation, "leases/lookup/")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.Data == nil {
t.Fatalf("bad: %#v", resp)
}
var keys []string
if err := mapstructure.WeakDecode(resp.Data["keys"], &keys); err != nil {
t.Fatalf("err: %v", err)
}
if len(keys) != 1 {
t.Fatalf("Expected 1 subkey lease, got %d: %#v", len(keys), keys)
}
if keys[0] != "secret/" {
t.Fatal("Expected only secret subkey")
}
// List lease
req = logical.TestRequest(t, logical.ListOperation, "leases/lookup/secret/foo")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.Data == nil {
t.Fatalf("bad: %#v", resp)
}
keys = []string{}
if err := mapstructure.WeakDecode(resp.Data["keys"], &keys); err != nil {
t.Fatalf("err: %v", err)
}
if len(keys) != 1 {
t.Fatalf("Expected 1 secret lease, got %d: %#v", len(keys), keys)
}
// Generate multiple leases
req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
req.ClientToken = root
req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}})
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
t.Fatalf("bad: %#v", resp)
}
req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
req.ClientToken = root
req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}})
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
t.Fatalf("bad: %#v", resp)
}
req = logical.TestRequest(t, logical.ListOperation, "leases/lookup/secret/foo")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.Data == nil {
t.Fatalf("bad: %#v", resp)
}
keys = []string{}
if err := mapstructure.WeakDecode(resp.Data["keys"], &keys); err != nil {
t.Fatalf("err: %v", err)
}
if len(keys) != 3 {
t.Fatalf("Expected 3 secret lease, got %d: %#v", len(keys), keys)
}
// Listing subkeys
req = logical.TestRequest(t, logical.UpdateOperation, "secret/bar")
req.Data["foo"] = "bar"
req.ClientToken = root
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %#v", resp)
}
// Read a key with a LeaseID
req = logical.TestRequest(t, logical.ReadOperation, "secret/bar")
req.ClientToken = root
req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}})
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
t.Fatalf("bad: %#v", resp)
}
req = logical.TestRequest(t, logical.ListOperation, "leases/lookup/secret")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.Data == nil {
t.Fatalf("bad: %#v", resp)
}
keys = []string{}
if err := mapstructure.WeakDecode(resp.Data["keys"], &keys); err != nil {
t.Fatalf("err: %v", err)
}
if len(keys) != 2 {
t.Fatalf("Expected 2 secret lease, got %d: %#v", len(keys), keys)
}
expected := []string{"bar/", "foo/"}
if !reflect.DeepEqual(expected, keys) {
t.Fatalf("exp: %#v, act: %#v", expected, keys)
}
}
func TestSystemBackend_renew(t *testing.T) {
core, b, root := testCoreSystemBackend(t)
// Create a key with a lease
req := logical.TestRequest(t, logical.UpdateOperation, "secret/foo")
req.Data["foo"] = "bar"
req.ClientToken = root
resp, err := core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %#v", resp)
}
// Read a key with a LeaseID
req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
req.ClientToken = root
req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}})
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
t.Fatalf("bad: %#v", resp)
}
// Attempt renew
req2 := logical.TestRequest(t, logical.UpdateOperation, "leases/renew/"+resp.Secret.LeaseID)
resp2, err := b.HandleRequest(namespace.RootContext(nil), req2)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
// Should get error about non-renewability
if resp2.Data["error"] != "lease is not renewable" {
t.Fatalf("bad: %#v", resp)
}
// Add a TTL to the lease
req = logical.TestRequest(t, logical.UpdateOperation, "secret/foo")
req.Data["foo"] = "bar"
req.Data["ttl"] = "180s"
req.ClientToken = root
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %#v", resp)
}
// Read a key with a LeaseID
req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
req.ClientToken = root
req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}})
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
t.Fatalf("bad: %#v", resp)
}
// Attempt renew
req2 = logical.TestRequest(t, logical.UpdateOperation, "leases/renew/"+resp.Secret.LeaseID)
resp2, err = b.HandleRequest(namespace.RootContext(nil), req2)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp2.IsError() {
t.Fatalf("got an error")
}
if resp2.Data == nil {
t.Fatal("nil data")
}
if resp.Secret.TTL != 180*time.Second {
t.Fatalf("bad lease duration: %v", resp.Secret.TTL)
}
// Test the other route path
req2 = logical.TestRequest(t, logical.UpdateOperation, "leases/renew")
req2.Data["lease_id"] = resp.Secret.LeaseID
resp2, err = b.HandleRequest(namespace.RootContext(nil), req2)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp2.IsError() {
t.Fatalf("got an error")
}
if resp2.Data == nil {
t.Fatal("nil data")
}
if resp.Secret.TTL != 180*time.Second {
t.Fatalf("bad lease duration: %v", resp.Secret.TTL)
}
// Test orig path
req2 = logical.TestRequest(t, logical.UpdateOperation, "renew")
req2.Data["lease_id"] = resp.Secret.LeaseID
resp2, err = b.HandleRequest(namespace.RootContext(nil), req2)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp2.IsError() {
t.Fatalf("got an error")
}
if resp2.Data == nil {
t.Fatal("nil data")
}
if resp.Secret.TTL != time.Second*180 {
t.Fatalf("bad lease duration: %v", resp.Secret.TTL)
}
}
func TestSystemBackend_renew_invalidID(t *testing.T) {
b := testSystemBackend(t)
// Attempt renew
req := logical.TestRequest(t, logical.UpdateOperation, "leases/renew/foobarbaz")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if resp.Data["error"] != "lease not found" {
t.Fatalf("bad: %v", resp)
}
// Attempt renew with other method
req = logical.TestRequest(t, logical.UpdateOperation, "leases/renew")
req.Data["lease_id"] = "foobarbaz"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if resp.Data["error"] != "lease not found" {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_renew_invalidID_origUrl(t *testing.T) {
b := testSystemBackend(t)
// Attempt renew
req := logical.TestRequest(t, logical.UpdateOperation, "renew/foobarbaz")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if resp.Data["error"] != "lease not found" {
t.Fatalf("bad: %v", resp)
}
// Attempt renew with other method
req = logical.TestRequest(t, logical.UpdateOperation, "renew")
req.Data["lease_id"] = "foobarbaz"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if resp.Data["error"] != "lease not found" {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_revoke(t *testing.T) {
core, b, root := testCoreSystemBackend(t)
// Create a key with a lease
req := logical.TestRequest(t, logical.UpdateOperation, "secret/foo")
req.Data["foo"] = "bar"
req.Data["lease"] = "1h"
req.ClientToken = root
resp, err := core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %#v", resp)
}
// Read a key with a LeaseID
req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
req.ClientToken = root
req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}})
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
t.Fatalf("bad: %#v", resp)
}
// Attempt revoke
req2 := logical.TestRequest(t, logical.UpdateOperation, "revoke/"+resp.Secret.LeaseID)
resp2, err := b.HandleRequest(namespace.RootContext(nil), req2)
if err != nil {
t.Fatalf("err: %v %#v", err, resp2)
}
if resp2 != nil {
t.Fatalf("bad: %#v", resp)
}
// Attempt renew
req3 := logical.TestRequest(t, logical.UpdateOperation, "renew/"+resp.Secret.LeaseID)
resp3, err := b.HandleRequest(namespace.RootContext(nil), req3)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if resp3.Data["error"] != "lease not found" {
t.Fatalf("bad: %v", *resp3)
}
// Read a key with a LeaseID
req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
req.ClientToken = root
req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}})
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
t.Fatalf("bad: %#v", resp)
}
// Test the other route path
req2 = logical.TestRequest(t, logical.UpdateOperation, "revoke")
req2.Data["lease_id"] = resp.Secret.LeaseID
resp2, err = b.HandleRequest(namespace.RootContext(nil), req2)
if err != nil {
t.Fatalf("err: %v %#v", err, resp2)
}
if resp2 != nil {
t.Fatalf("bad: %#v", resp)
}
// Read a key with a LeaseID
req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
req.ClientToken = root
req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}})
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
t.Fatalf("bad: %#v", resp)
}
// Test the other route path
req2 = logical.TestRequest(t, logical.UpdateOperation, "leases/revoke")
req2.Data["lease_id"] = resp.Secret.LeaseID
resp2, err = b.HandleRequest(namespace.RootContext(nil), req2)
if err != nil {
t.Fatalf("err: %v %#v", err, resp2)
}
if resp2 != nil {
t.Fatalf("bad: %#v", resp)
}
}
func TestSystemBackend_revoke_invalidID(t *testing.T) {
b := testSystemBackend(t)
// Attempt revoke
req := logical.TestRequest(t, logical.UpdateOperation, "leases/revoke/foobarbaz")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
// Attempt revoke with other method
req = logical.TestRequest(t, logical.UpdateOperation, "leases/revoke")
req.Data["lease_id"] = "foobarbaz"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_revoke_invalidID_origUrl(t *testing.T) {
b := testSystemBackend(t)
// Attempt revoke
req := logical.TestRequest(t, logical.UpdateOperation, "revoke/foobarbaz")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
// Attempt revoke with other method
req = logical.TestRequest(t, logical.UpdateOperation, "revoke")
req.Data["lease_id"] = "foobarbaz"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_revokePrefix(t *testing.T) {
core, b, root := testCoreSystemBackend(t)
// Create a key with a lease
req := logical.TestRequest(t, logical.UpdateOperation, "secret/foo")
req.Data["foo"] = "bar"
req.Data["lease"] = "1h"
req.ClientToken = root
resp, err := core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %#v", resp)
}
// Read a key with a LeaseID
req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
req.ClientToken = root
req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}})
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
t.Fatalf("bad: %#v", resp)
}
// Attempt revoke
req2 := logical.TestRequest(t, logical.UpdateOperation, "leases/revoke-prefix/secret/")
resp2, err := b.HandleRequest(namespace.RootContext(nil), req2)
if err != nil {
t.Fatalf("err: %v %#v", err, resp2)
}
if resp2 != nil {
t.Fatalf("bad: %#v", resp)
}
// Attempt renew
req3 := logical.TestRequest(t, logical.UpdateOperation, "leases/renew/"+resp.Secret.LeaseID)
resp3, err := b.HandleRequest(namespace.RootContext(nil), req3)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if resp3.Data["error"] != "lease not found" {
t.Fatalf("bad: %v", *resp3)
}
}
func TestSystemBackend_revokePrefix_origUrl(t *testing.T) {
core, b, root := testCoreSystemBackend(t)
// Create a key with a lease
req := logical.TestRequest(t, logical.UpdateOperation, "secret/foo")
req.Data["foo"] = "bar"
req.Data["lease"] = "1h"
req.ClientToken = root
resp, err := core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %#v", resp)
}
// Read a key with a LeaseID
req = logical.TestRequest(t, logical.ReadOperation, "secret/foo")
req.ClientToken = root
req.SetTokenEntry(&logical.TokenEntry{ID: root, NamespaceID: "root", Policies: []string{"root"}})
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.Secret == nil || resp.Secret.LeaseID == "" {
t.Fatalf("bad: %#v", resp)
}
// Attempt revoke
req2 := logical.TestRequest(t, logical.UpdateOperation, "revoke-prefix/secret/")
resp2, err := b.HandleRequest(namespace.RootContext(nil), req2)
if err != nil {
t.Fatalf("err: %v %#v", err, resp2)
}
if resp2 != nil {
t.Fatalf("bad: %#v", resp)
}
// Attempt renew
req3 := logical.TestRequest(t, logical.UpdateOperation, "renew/"+resp.Secret.LeaseID)
resp3, err := b.HandleRequest(namespace.RootContext(nil), req3)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if resp3.Data["error"] != "lease not found" {
t.Fatalf("bad: %#v", *resp3)
}
}
func TestSystemBackend_revokePrefixAuth_newUrl(t *testing.T) {
core, _, _ := TestCoreUnsealed(t)
ts := core.tokenStore
bc := &logical.BackendConfig{
Logger: core.logger,
System: logical.StaticSystemView{
DefaultLeaseTTLVal: time.Hour * 24,
MaxLeaseTTLVal: time.Hour * 24 * 32,
},
}
b := NewSystemBackend(core, hclog.New(&hclog.LoggerOptions{}))
err := b.Backend.Setup(namespace.RootContext(nil), bc)
if err != nil {
t.Fatal(err)
}
exp := ts.expiration
te := &logical.TokenEntry{
ID: "foo",
Path: "auth/github/login/bar",
TTL: time.Hour,
NamespaceID: namespace.RootNamespaceID,
}
testMakeTokenDirectly(t, ts, te)
te, err = ts.Lookup(namespace.RootContext(nil), "foo")
if err != nil {
t.Fatal(err)
}
if te == nil {
t.Fatal("token entry was nil")
}
// Create a new token
auth := &logical.Auth{
ClientToken: te.ID,
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour,
},
}
err = exp.RegisterAuth(namespace.RootContext(nil), te, auth)
if err != nil {
t.Fatalf("err: %v", err)
}
req := logical.TestRequest(t, logical.UpdateOperation, "leases/revoke-prefix/auth/github/")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v %v", err, resp)
}
if resp != nil {
t.Fatalf("bad: %#v", resp)
}
te, err = ts.Lookup(namespace.RootContext(nil), te.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if te != nil {
t.Fatalf("bad: %v", te)
}
}
func TestSystemBackend_revokePrefixAuth_origUrl(t *testing.T) {
core, _, _ := TestCoreUnsealed(t)
ts := core.tokenStore
bc := &logical.BackendConfig{
Logger: core.logger,
System: logical.StaticSystemView{
DefaultLeaseTTLVal: time.Hour * 24,
MaxLeaseTTLVal: time.Hour * 24 * 32,
},
}
b := NewSystemBackend(core, hclog.New(&hclog.LoggerOptions{}))
err := b.Backend.Setup(namespace.RootContext(nil), bc)
if err != nil {
t.Fatal(err)
}
exp := ts.expiration
te := &logical.TokenEntry{
ID: "foo",
Path: "auth/github/login/bar",
TTL: time.Hour,
NamespaceID: namespace.RootNamespaceID,
}
testMakeTokenDirectly(t, ts, te)
te, err = ts.Lookup(namespace.RootContext(nil), "foo")
if err != nil {
t.Fatal(err)
}
if te == nil {
t.Fatal("token entry was nil")
}
// Create a new token
auth := &logical.Auth{
ClientToken: te.ID,
LeaseOptions: logical.LeaseOptions{
TTL: time.Hour,
},
}
err = exp.RegisterAuth(namespace.RootContext(nil), te, auth)
if err != nil {
t.Fatalf("err: %v", err)
}
req := logical.TestRequest(t, logical.UpdateOperation, "revoke-prefix/auth/github/")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v %v", err, resp)
}
if resp != nil {
t.Fatalf("bad: %#v", resp)
}
te, err = ts.Lookup(namespace.RootContext(nil), te.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if te != nil {
t.Fatalf("bad: %v", te)
}
}
func TestSystemBackend_authTable(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.ReadOperation, "auth")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
exp := map[string]interface{}{
"token/": map[string]interface{}{
"type": "token",
"external_entropy_access": false,
"description": "token based credentials",
"accessor": resp.Data["token/"].(map[string]interface{})["accessor"],
"uuid": resp.Data["token/"].(map[string]interface{})["uuid"],
"config": map[string]interface{}{
"default_lease_ttl": int64(0),
"max_lease_ttl": int64(0),
"force_no_cache": false,
"token_type": "default-service",
},
"local": false,
"seal_wrap": false,
"options": map[string]string(nil),
},
}
if diff := deep.Equal(resp.Data, exp); diff != nil {
t.Fatal(diff)
}
}
func TestSystemBackend_enableAuth(t *testing.T) {
c, b, _ := testCoreSystemBackend(t)
c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
return &NoopBackend{BackendType: logical.TypeCredential}, nil
}
req := logical.TestRequest(t, logical.UpdateOperation, "auth/foo")
req.Data["type"] = "noop"
req.Data["config"] = map[string]interface{}{
"default_lease_ttl": "35m",
"max_lease_ttl": "45m",
}
req.Data["local"] = true
req.Data["seal_wrap"] = true
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
req = logical.TestRequest(t, logical.ReadOperation, "auth")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil {
t.Fatal("resp is nil")
}
exp := map[string]interface{}{
"foo/": map[string]interface{}{
"type": "noop",
"external_entropy_access": false,
"description": "",
"accessor": resp.Data["foo/"].(map[string]interface{})["accessor"],
"uuid": resp.Data["foo/"].(map[string]interface{})["uuid"],
"config": map[string]interface{}{
"default_lease_ttl": int64(2100),
"max_lease_ttl": int64(2700),
"force_no_cache": false,
"token_type": "default-service",
},
"local": true,
"seal_wrap": true,
"options": map[string]string{},
},
"token/": map[string]interface{}{
"type": "token",
"external_entropy_access": false,
"description": "token based credentials",
"accessor": resp.Data["token/"].(map[string]interface{})["accessor"],
"uuid": resp.Data["token/"].(map[string]interface{})["uuid"],
"config": map[string]interface{}{
"default_lease_ttl": int64(0),
"max_lease_ttl": int64(0),
"force_no_cache": false,
"token_type": "default-service",
},
"local": false,
"seal_wrap": false,
"options": map[string]string(nil),
},
}
if diff := deep.Equal(resp.Data, exp); diff != nil {
t.Fatal(diff)
}
}
func TestSystemBackend_enableAuth_invalid(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.UpdateOperation, "auth/foo")
req.Data["type"] = "nope"
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if resp.Data["error"] != `plugin not found in the catalog: nope` {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_disableAuth(t *testing.T) {
c, b, _ := testCoreSystemBackend(t)
c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
return &NoopBackend{}, nil
}
// Register the backend
req := logical.TestRequest(t, logical.UpdateOperation, "auth/foo")
req.Data["type"] = "noop"
b.HandleRequest(namespace.RootContext(nil), req)
// Deregister it
req = logical.TestRequest(t, logical.DeleteOperation, "auth/foo")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_tuneAuth(t *testing.T) {
c, b, _ := testCoreSystemBackend(t)
c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
return &NoopBackend{BackendType: logical.TypeCredential}, nil
}
req := logical.TestRequest(t, logical.ReadOperation, "auth/token/tune")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil {
t.Fatal("resp is nil")
}
exp := map[string]interface{}{
"description": "token based credentials",
"default_lease_ttl": int(2764800),
"max_lease_ttl": int(2764800),
"force_no_cache": false,
"token_type": "default-service",
}
if diff := deep.Equal(resp.Data, exp); diff != nil {
t.Fatal(diff)
}
req = logical.TestRequest(t, logical.UpdateOperation, "auth/token/tune")
req.Data["description"] = ""
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
req = logical.TestRequest(t, logical.ReadOperation, "auth/token/tune")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil {
t.Fatal("resp is nil")
}
if resp.Data["description"] != "" {
t.Fatalf("got: %#v expect: %#v", resp.Data["description"], "")
}
}
func TestSystemBackend_policyList(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.ReadOperation, "policy")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
exp := map[string]interface{}{
"keys": []string{"default", "root"},
"policies": []string{"default", "root"},
}
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
}
}
func TestSystemBackend_policyCRUD(t *testing.T) {
b := testSystemBackend(t)
// Create the policy
rules := `path "foo/" { policy = "read" }`
req := logical.TestRequest(t, logical.UpdateOperation, "policy/Foo")
req.Data["rules"] = rules
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v %#v", err, resp)
}
if resp != nil && (resp.IsError() || len(resp.Data) > 0) {
t.Fatalf("bad: %#v", resp)
}
// Read the policy
req = logical.TestRequest(t, logical.ReadOperation, "policy/foo")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
exp := map[string]interface{}{
"name": "foo",
"rules": rules,
}
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
}
// Read, and make sure that case has been normalized
req = logical.TestRequest(t, logical.ReadOperation, "policy/Foo")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
exp = map[string]interface{}{
"name": "foo",
"rules": rules,
}
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
}
// List the policies
req = logical.TestRequest(t, logical.ReadOperation, "policy")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
exp = map[string]interface{}{
"keys": []string{"default", "foo", "root"},
"policies": []string{"default", "foo", "root"},
}
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
}
// Delete the policy
req = logical.TestRequest(t, logical.DeleteOperation, "policy/foo")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %#v", resp)
}
// Read the policy (deleted)
req = logical.TestRequest(t, logical.ReadOperation, "policy/foo")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %#v", resp)
}
// List the policies (deleted)
req = logical.TestRequest(t, logical.ReadOperation, "policy")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
exp = map[string]interface{}{
"keys": []string{"default", "root"},
"policies": []string{"default", "root"},
}
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
}
}
func TestSystemBackend_enableAudit(t *testing.T) {
c, b, _ := testCoreSystemBackend(t)
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
return &NoopAudit{
Config: config,
}, nil
}
req := logical.TestRequest(t, logical.UpdateOperation, "audit/foo")
req.Data["type"] = "noop"
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_auditHash(t *testing.T) {
c, b, _ := testCoreSystemBackend(t)
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
view := &logical.InmemStorage{}
view.Put(namespace.RootContext(nil), &logical.StorageEntry{
Key: "salt",
Value: []byte("foo"),
})
config.SaltView = view
config.SaltConfig = &salt.Config{
HMAC: sha256.New,
HMACType: "hmac-sha256",
Location: salt.DefaultLocation,
}
return &NoopAudit{
Config: config,
}, nil
}
req := logical.TestRequest(t, logical.UpdateOperation, "audit/foo")
req.Data["type"] = "noop"
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
req = logical.TestRequest(t, logical.UpdateOperation, "audit-hash/foo")
req.Data["input"] = "bar"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil || resp.Data == nil {
t.Fatalf("response or its data was nil")
}
hash, ok := resp.Data["hash"]
if !ok {
t.Fatalf("did not get hash back in response, response was %#v", resp.Data)
}
if hash.(string) != "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317" {
t.Fatalf("bad hash back: %s", hash.(string))
}
}
func TestSystemBackend_enableAudit_invalid(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.UpdateOperation, "audit/foo")
req.Data["type"] = "nope"
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if resp.Data["error"] != `unknown backend type: "nope"` {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_auditTable(t *testing.T) {
c, b, _ := testCoreSystemBackend(t)
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
return &NoopAudit{
Config: config,
}, nil
}
req := logical.TestRequest(t, logical.UpdateOperation, "audit/foo")
req.Data["type"] = "noop"
req.Data["description"] = "testing"
req.Data["options"] = map[string]interface{}{
"foo": "bar",
}
req.Data["local"] = true
b.HandleRequest(namespace.RootContext(nil), req)
req = logical.TestRequest(t, logical.ReadOperation, "audit")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
exp := map[string]interface{}{
"foo/": map[string]interface{}{
"path": "foo/",
"type": "noop",
"description": "testing",
"options": map[string]string{
"foo": "bar",
},
"local": true,
},
}
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
}
}
func TestSystemBackend_disableAudit(t *testing.T) {
c, b, _ := testCoreSystemBackend(t)
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
return &NoopAudit{
Config: config,
}, nil
}
req := logical.TestRequest(t, logical.UpdateOperation, "audit/foo")
req.Data["type"] = "noop"
req.Data["description"] = "testing"
req.Data["options"] = map[string]interface{}{
"foo": "bar",
}
b.HandleRequest(namespace.RootContext(nil), req)
// Deregister it
req = logical.TestRequest(t, logical.DeleteOperation, "audit/foo")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_rawRead_Compressed(t *testing.T) {
b := testSystemBackendRaw(t)
req := logical.TestRequest(t, logical.ReadOperation, "raw/core/mounts")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if !strings.HasPrefix(resp.Data["value"].(string), "{\"type\":\"mounts\"") {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_rawRead_Protected(t *testing.T) {
b := testSystemBackendRaw(t)
req := logical.TestRequest(t, logical.ReadOperation, "raw/"+keyringPath)
_, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
}
func TestSystemBackend_rawWrite_Protected(t *testing.T) {
b := testSystemBackendRaw(t)
req := logical.TestRequest(t, logical.UpdateOperation, "raw/"+keyringPath)
_, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
}
func TestSystemBackend_rawReadWrite(t *testing.T) {
_, b, _ := testCoreSystemBackendRaw(t)
req := logical.TestRequest(t, logical.UpdateOperation, "raw/sys/policy/test")
req.Data["value"] = `path "secret/" { policy = "read" }`
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
// Read via raw API
req = logical.TestRequest(t, logical.ReadOperation, "raw/sys/policy/test")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if !strings.HasPrefix(resp.Data["value"].(string), "path") {
t.Fatalf("bad: %v", resp)
}
// Note: since the upgrade code is gone that upgraded from 0.1, we can't
// simply parse this out directly via GetPolicy, so the test now ends here.
}
func TestSystemBackend_rawDelete_Protected(t *testing.T) {
b := testSystemBackendRaw(t)
req := logical.TestRequest(t, logical.DeleteOperation, "raw/"+keyringPath)
_, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
}
func TestSystemBackend_rawDelete(t *testing.T) {
c, b, _ := testCoreSystemBackendRaw(t)
// set the policy!
p := &Policy{
Name: "test",
Type: PolicyTypeACL,
namespace: namespace.RootNamespace,
}
err := c.policyStore.SetPolicy(namespace.RootContext(nil), p)
if err != nil {
t.Fatalf("err: %v", err)
}
// Delete the policy
req := logical.TestRequest(t, logical.DeleteOperation, "raw/sys/policy/test")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
// Policy should be gone
c.policyStore.tokenPoliciesLRU.Purge()
out, err := c.policyStore.GetPolicy(namespace.RootContext(nil), "test", PolicyTypeToken)
if err != nil {
t.Fatalf("err: %v", err)
}
if out != nil {
t.Fatalf("policy should be gone")
}
}
func TestSystemBackend_keyStatus(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.ReadOperation, "key-status")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
exp := map[string]interface{}{
"term": 1,
}
delete(resp.Data, "install_time")
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
}
}
func TestSystemBackend_rotate(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.UpdateOperation, "rotate")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
req = logical.TestRequest(t, logical.ReadOperation, "key-status")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
exp := map[string]interface{}{
"term": 2,
}
delete(resp.Data, "install_time")
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
}
}
func testSystemBackend(t *testing.T) logical.Backend {
c, _, _ := TestCoreUnsealed(t)
return c.systemBackend
}
func testSystemBackendRaw(t *testing.T) logical.Backend {
c, _, _ := TestCoreUnsealedRaw(t)
return c.systemBackend
}
func testCoreSystemBackend(t *testing.T) (*Core, logical.Backend, string) {
c, _, root := TestCoreUnsealed(t)
return c, c.systemBackend, root
}
func testCoreSystemBackendRaw(t *testing.T) (*Core, logical.Backend, string) {
c, _, root := TestCoreUnsealedRaw(t)
return c, c.systemBackend, root
}
func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) {
c, b, _ := testCoreSystemBackend(t)
// Bootstrap the pluginCatalog
sym, err := filepath.EvalSymlinks(os.TempDir())
if err != nil {
t.Fatalf("error: %v", err)
}
c.pluginCatalog.directory = sym
req := logical.TestRequest(t, logical.ListOperation, "plugins/catalog/database")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if len(resp.Data["keys"].([]string)) != len(c.builtinRegistry.Keys(consts.PluginTypeDatabase)) {
t.Fatalf("Wrong number of plugins, got %d, expected %d", len(resp.Data["keys"].([]string)), len(builtinplugins.Registry.Keys(consts.PluginTypeDatabase)))
}
req = logical.TestRequest(t, logical.ReadOperation, "plugins/catalog/database/mysql-database-plugin")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
actualRespData := resp.Data
expectedRespData := map[string]interface{}{
"name": "mysql-database-plugin",
"command": "",
"args": []string(nil),
"sha256": "",
"builtin": true,
}
if !reflect.DeepEqual(actualRespData, expectedRespData) {
t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", actualRespData, expectedRespData)
}
// Set a plugin
file, err := ioutil.TempFile(os.TempDir(), "temp")
if err != nil {
t.Fatal(err)
}
defer file.Close()
// Check we can only specify args in one of command or args.
command := fmt.Sprintf("%s --test", filepath.Base(file.Name()))
req = logical.TestRequest(t, logical.UpdateOperation, "plugins/catalog/database/test-plugin")
req.Data["args"] = []string{"--foo"}
req.Data["sha_256"] = hex.EncodeToString([]byte{'1'})
req.Data["command"] = command
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp.Error().Error() != "must not specify args in command and args field" {
t.Fatalf("err: %v", resp.Error())
}
delete(req.Data, "args")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || resp.Error() != nil {
t.Fatalf("err: %v %v", err, resp.Error())
}
req = logical.TestRequest(t, logical.ReadOperation, "plugins/catalog/database/test-plugin")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
actual := resp.Data
expected := map[string]interface{}{
"name": "test-plugin",
"command": filepath.Base(file.Name()),
"args": []string{"--test"},
"sha256": "31",
"builtin": false,
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", actual, expected)
}
// Delete plugin
req = logical.TestRequest(t, logical.DeleteOperation, "plugins/catalog/database/test-plugin")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
req = logical.TestRequest(t, logical.ReadOperation, "plugins/catalog/database/test-plugin")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if resp != nil || err != nil {
t.Fatalf("expected nil response, plugin not deleted correctly got resp: %v, err: %v", resp, err)
}
}
func TestSystemBackend_ToolsHash(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.UpdateOperation, "tools/hash")
req.Data = map[string]interface{}{
"input": "dGhlIHF1aWNrIGJyb3duIGZveA==",
}
_, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
doRequest := func(req *logical.Request, errExpected bool, expected string) {
t.Helper()
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil && !errExpected {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected non-nil response")
}
if errExpected {
if !resp.IsError() {
t.Fatalf("bad: got error response: %#v", *resp)
}
return
}
if resp.IsError() {
t.Fatalf("bad: got error response: %#v", *resp)
}
sum, ok := resp.Data["sum"]
if !ok {
t.Fatal("no sum key found in returned data")
}
if sum.(string) != expected {
t.Fatal("mismatched hashes")
}
}
// Test defaults -- sha2-256
doRequest(req, false, "9ecb36561341d18eb65484e833efea61edc74b84cf5e6ae1b81c63533e25fc8f")
// Test algorithm selection in the path
req.Path = "tools/hash/sha2-224"
doRequest(req, false, "ea074a96cabc5a61f8298a2c470f019074642631a49e1c5e2f560865")
// Reset and test algorithm selection in the data
req.Path = "tools/hash"
req.Data["algorithm"] = "sha2-224"
doRequest(req, false, "ea074a96cabc5a61f8298a2c470f019074642631a49e1c5e2f560865")
req.Data["algorithm"] = "sha2-384"
doRequest(req, false, "15af9ec8be783f25c583626e9491dbf129dd6dd620466fdf05b3a1d0bb8381d30f4d3ec29f923ff1e09a0f6b337365a6")
req.Data["algorithm"] = "sha2-512"
doRequest(req, false, "d9d380f29b97ad6a1d92e987d83fa5a02653301e1006dd2bcd51afa59a9147e9caedaf89521abc0f0b682adcd47fb512b8343c834a32f326fe9bef00542ce887")
// Test returning as base64
req.Data["format"] = "base64"
doRequest(req, false, "2dOA8puXrWodkumH2D+loCZTMB4QBt0rzVGvpZqRR+nK7a+JUhq8DwtoKtzUf7USuDQ8g0oy8yb+m+8AVCzohw==")
// Test bad input/format/algorithm
req.Data["format"] = "base92"
doRequest(req, true, "")
req.Data["format"] = "hex"
req.Data["algorithm"] = "foobar"
doRequest(req, true, "")
req.Data["algorithm"] = "sha2-256"
req.Data["input"] = "foobar"
doRequest(req, true, "")
}
func TestSystemBackend_ToolsRandom(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.UpdateOperation, "tools/random")
_, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
doRequest := func(req *logical.Request, errExpected bool, format string, numBytes int) {
t.Helper()
getResponse := func() []byte {
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil && !errExpected {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected non-nil response")
}
if errExpected {
if !resp.IsError() {
t.Fatalf("bad: got error response: %#v", *resp)
}
return nil
}
if resp.IsError() {
t.Fatalf("bad: got error response: %#v", *resp)
}
if _, ok := resp.Data["random_bytes"]; !ok {
t.Fatal("no random_bytes found in response")
}
outputStr := resp.Data["random_bytes"].(string)
var outputBytes []byte
switch format {
case "base64":
outputBytes, err = base64.StdEncoding.DecodeString(outputStr)
case "hex":
outputBytes, err = hex.DecodeString(outputStr)
default:
t.Fatal("unknown format")
}
if err != nil {
t.Fatal(err)
}
return outputBytes
}
rand1 := getResponse()
// Expected error
if rand1 == nil {
return
}
rand2 := getResponse()
if len(rand1) != numBytes || len(rand2) != numBytes {
t.Fatal("length of output random bytes not what is expected")
}
if reflect.DeepEqual(rand1, rand2) {
t.Fatal("found identical ouputs")
}
}
// Test defaults
doRequest(req, false, "base64", 32)
// Test size selection in the path
req.Path = "tools/random/24"
req.Data["format"] = "hex"
doRequest(req, false, "hex", 24)
// Test bad input/format
req.Path = "tools/random"
req.Data["format"] = "base92"
doRequest(req, true, "", 0)
req.Data["format"] = "hex"
req.Data["bytes"] = -1
doRequest(req, true, "", 0)
req.Data["format"] = "hex"
req.Data["bytes"] = maxBytes + 1
doRequest(req, true, "", 0)
}
func TestSystemBackend_InternalUIMounts(t *testing.T) {
_, b, rootToken := testCoreSystemBackend(t)
// Ensure no entries are in the endpoint as a starting point
req := logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
exp := map[string]interface{}{
"secret": map[string]interface{}{},
"auth": map[string]interface{}{},
}
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
}
req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts")
req.ClientToken = rootToken
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
exp = map[string]interface{}{
"secret": map[string]interface{}{
"secret/": map[string]interface{}{
"type": "kv",
"external_entropy_access": false,
"description": "key/value secret storage",
"accessor": resp.Data["secret"].(map[string]interface{})["secret/"].(map[string]interface{})["accessor"],
"uuid": resp.Data["secret"].(map[string]interface{})["secret/"].(map[string]interface{})["uuid"],
"config": map[string]interface{}{
"default_lease_ttl": resp.Data["secret"].(map[string]interface{})["secret/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
"max_lease_ttl": resp.Data["secret"].(map[string]interface{})["secret/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
"force_no_cache": false,
},
"local": false,
"seal_wrap": false,
"options": map[string]string{
"version": "1",
},
},
"sys/": map[string]interface{}{
"type": "system",
"external_entropy_access": false,
"description": "system endpoints used for control, policy and debugging",
"accessor": resp.Data["secret"].(map[string]interface{})["sys/"].(map[string]interface{})["accessor"],
"uuid": resp.Data["secret"].(map[string]interface{})["sys/"].(map[string]interface{})["uuid"],
"config": map[string]interface{}{
"default_lease_ttl": resp.Data["secret"].(map[string]interface{})["sys/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
"max_lease_ttl": resp.Data["secret"].(map[string]interface{})["sys/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
"force_no_cache": false,
"passthrough_request_headers": []string{"Accept"},
},
"local": false,
"seal_wrap": false,
"options": map[string]string(nil),
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
"type": "cubbyhole",
"external_entropy_access": false,
"accessor": resp.Data["secret"].(map[string]interface{})["cubbyhole/"].(map[string]interface{})["accessor"],
"uuid": resp.Data["secret"].(map[string]interface{})["cubbyhole/"].(map[string]interface{})["uuid"],
"config": map[string]interface{}{
"default_lease_ttl": resp.Data["secret"].(map[string]interface{})["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
"max_lease_ttl": resp.Data["secret"].(map[string]interface{})["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
"force_no_cache": false,
},
"local": true,
"seal_wrap": false,
"options": map[string]string(nil),
},
"identity/": map[string]interface{}{
"description": "identity store",
"type": "identity",
"external_entropy_access": false,
"accessor": resp.Data["secret"].(map[string]interface{})["identity/"].(map[string]interface{})["accessor"],
"uuid": resp.Data["secret"].(map[string]interface{})["identity/"].(map[string]interface{})["uuid"],
"config": map[string]interface{}{
"default_lease_ttl": resp.Data["secret"].(map[string]interface{})["identity/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(int64),
"max_lease_ttl": resp.Data["secret"].(map[string]interface{})["identity/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
"force_no_cache": false,
},
"local": false,
"seal_wrap": false,
"options": map[string]string(nil),
},
},
"auth": map[string]interface{}{
"token/": map[string]interface{}{
"options": map[string]string(nil),
"config": map[string]interface{}{
"default_lease_ttl": int64(0),
"max_lease_ttl": int64(0),
"force_no_cache": false,
"token_type": "default-service",
},
"type": "token",
"external_entropy_access": false,
"description": "token based credentials",
"accessor": resp.Data["auth"].(map[string]interface{})["token/"].(map[string]interface{})["accessor"],
"uuid": resp.Data["auth"].(map[string]interface{})["token/"].(map[string]interface{})["uuid"],
"local": false,
"seal_wrap": false,
},
},
}
if diff := deep.Equal(resp.Data, exp); diff != nil {
t.Fatal(diff)
}
// Mount-tune an auth mount
req = logical.TestRequest(t, logical.UpdateOperation, "auth/token/tune")
req.Data["listing_visibility"] = "unauth"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if resp.IsError() || err != nil {
t.Fatalf("resp.Error: %v, err:%v", resp.Error(), err)
}
// Mount-tune a secret mount
req = logical.TestRequest(t, logical.UpdateOperation, "mounts/secret/tune")
req.Data["listing_visibility"] = "unauth"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if resp.IsError() || err != nil {
t.Fatalf("resp.Error: %v, err:%v", resp.Error(), err)
}
req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts")
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
exp = map[string]interface{}{
"secret": map[string]interface{}{
"secret/": map[string]interface{}{
"type": "kv",
"description": "key/value secret storage",
"options": map[string]string{"version": "1"},
},
},
"auth": map[string]interface{}{
"token/": map[string]interface{}{
"type": "token",
"description": "token based credentials",
"options": map[string]string(nil),
},
},
}
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
}
}
func TestSystemBackend_InternalUIMount(t *testing.T) {
core, b, rootToken := testCoreSystemBackend(t)
req := logical.TestRequest(t, logical.UpdateOperation, "policy/secret")
req.ClientToken = rootToken
req.Data = map[string]interface{}{
"rules": `path "secret/foo/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}`,
}
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("Bad %#v %#v", err, resp)
}
req = logical.TestRequest(t, logical.UpdateOperation, "mounts/kv")
req.ClientToken = rootToken
req.Data = map[string]interface{}{
"type": "kv",
}
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("Bad %#v %#v", err, resp)
}
req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts/kv/bar")
req.ClientToken = rootToken
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("Bad %#v %#v", err, resp)
}
if resp.Data["type"] != "kv" {
t.Fatalf("Bad Response: %#v", resp)
}
testMakeServiceTokenViaBackend(t, core.tokenStore, rootToken, "tokenid", "", []string{"secret"})
req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts/kv")
req.ClientToken = "tokenid"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrPermissionDenied {
t.Fatal("expected permission denied error")
}
req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts/secret")
req.ClientToken = "tokenid"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("Bad %#v %#v", err, resp)
}
if resp.Data["type"] != "kv" {
t.Fatalf("Bad Response: %#v", resp)
}
req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts/sys")
req.ClientToken = "tokenid"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("Bad %#v %#v", err, resp)
}
if resp.Data["type"] != "system" {
t.Fatalf("Bad Response: %#v", resp)
}
req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts/non-existent")
req.ClientToken = "tokenid"
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != logical.ErrPermissionDenied {
t.Fatal("expected permission denied error")
}
}
func TestSystemBackend_OpenAPI(t *testing.T) {
_, b, rootToken := testCoreSystemBackend(t)
var oapi map[string]interface{}
// Ensure no paths are reported if there is no token
req := logical.TestRequest(t, logical.ReadOperation, "internal/specs/openapi")
resp, err := b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
body := resp.Data["http_raw_body"].([]byte)
err = jsonutil.DecodeJSON(body, &oapi)
if err != nil {
t.Fatalf("err: %v", err)
}
exp := map[string]interface{}{
"openapi": framework.OASVersion,
"info": map[string]interface{}{
"title": "HashiCorp Vault API",
"description": "HTTP API that gives you full access to Vault. All API routes are prefixed with `/v1/`.",
"version": version.GetVersion().Version,
"license": map[string]interface{}{
"name": "Mozilla Public License 2.0",
"url": "https://www.mozilla.org/en-US/MPL/2.0",
},
},
"paths": map[string]interface{}{},
}
if diff := deep.Equal(oapi, exp); diff != nil {
t.Fatal(diff)
}
// Check that default paths are present with a root token
req = logical.TestRequest(t, logical.ReadOperation, "internal/specs/openapi")
req.ClientToken = rootToken
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
body = resp.Data["http_raw_body"].([]byte)
err = jsonutil.DecodeJSON(body, &oapi)
if err != nil {
t.Fatalf("err: %v", err)
}
doc, err := framework.NewOASDocumentFromMap(oapi)
if err != nil {
t.Fatal(err)
}
pathSamples := []struct {
path string
tag string
}{
{"/auth/token/lookup", "auth"},
{"/cubbyhole/{path}", "secrets"},
{"/identity/group/id", "identity"},
{"/secret/.*", "secrets"}, // TODO update after kv repo update
{"/sys/policy", "system"},
}
for _, path := range pathSamples {
if doc.Paths[path.path] == nil {
t.Fatalf("didn't find expected path '%s'.", path)
}
tag := doc.Paths[path.path].Get.Tags[0]
if tag != path.tag {
t.Fatalf("path: %s; expected tag: %s, actual: %s", path.path, tag, path.tag)
}
}
// Simple sanity check of response size (which is much larger than most
// Vault responses), mainly to catch mass omission of expected path data.
minLen := 70000
if len(body) < minLen {
t.Fatalf("response size too small; expected: min %d, actual: %d", minLen, len(body))
}
// Test path-help response
req = logical.TestRequest(t, logical.HelpOperation, "rotate")
req.ClientToken = rootToken
resp, err = b.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
doc = resp.Data["openapi"].(*framework.OASDocument)
if len(doc.Paths) != 1 {
t.Fatalf("expected 1 path, actual: %d", len(doc.Paths))
}
if doc.Paths["/rotate"] == nil {
t.Fatalf("expected to find path '/rotate'")
}
}
func TestSystemBackend_PathWildcardPreflight(t *testing.T) {
core, b, _ := testCoreSystemBackend(t)
ctx := namespace.RootContext(nil)
// Add another mount
me := &MountEntry{
Table: mountTableType,
Path: sanitizeMountPath("kv-v1"),
Type: "kv",
Options: map[string]string{"version": "1"},
}
if err := core.mount(ctx, me); err != nil {
t.Fatal(err)
}
// Create the policy, designed to fail
rules := `path "foo" { capabilities = ["read"] }`
req := logical.TestRequest(t, logical.UpdateOperation, "policy/foo")
req.Data["rules"] = rules
resp, err := b.HandleRequest(ctx, req)
if err != nil {
t.Fatalf("err: %v %#v", err, resp)
}
if resp != nil && (resp.IsError() || len(resp.Data) > 0) {
t.Fatalf("bad: %#v", resp)
}
if err := core.identityStore.upsertEntity(ctx, &identity.Entity{
ID: "abcd",
Name: "abcd",
BucketKey: "abcd",
}, nil, false); err != nil {
t.Fatal(err)
}
te := &logical.TokenEntry{
TTL: 300 * time.Second,
EntityID: "abcd",
Policies: []string{"default", "foo"},
NamespaceID: namespace.RootNamespaceID,
}
if err := core.tokenStore.create(ctx, te); err != nil {
t.Fatal(err)
}
t.Logf("token id: %s", te.ID)
if err := core.expiration.RegisterAuth(ctx, te, &logical.Auth{
LeaseOptions: logical.LeaseOptions{
TTL: te.TTL,
},
ClientToken: te.ID,
Accessor: te.Accessor,
Orphan: true,
}); err != nil {
t.Fatal(err)
}
// Check the mount access func
req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts/kv-v1/baz")
req.ClientToken = te.ID
resp, err = b.HandleRequest(ctx, req)
if err == nil || !strings.Contains(err.Error(), "permission denied") {
t.Fatalf("expected 403, got err: %v", err)
}
// Modify policy to pass
rules = `path "kv-v1/+" { capabilities = ["read"] }`
req = logical.TestRequest(t, logical.UpdateOperation, "policy/foo")
req.Data["rules"] = rules
resp, err = b.HandleRequest(ctx, req)
if err != nil {
t.Fatalf("err: %v %#v", err, resp)
}
if resp != nil && (resp.IsError() || len(resp.Data) > 0) {
t.Fatalf("bad: %#v", resp)
}
// Check the mount access func again
req = logical.TestRequest(t, logical.ReadOperation, "internal/ui/mounts/kv-v1/baz")
req.ClientToken = te.ID
resp, err = b.HandleRequest(ctx, req)
if err != nil {
t.Fatalf("err: %v", err)
}
}
func TestHandlePoliciesPasswordSet(t *testing.T) {
type testCase struct {
inputData *framework.FieldData
storage *logical.InmemStorage
expectedResp *logical.Response
expectErr bool
expectedStore map[string]*logical.StorageEntry
}
tests := map[string]testCase{
"missing policy name": {
inputData: passwordPoliciesFieldData(map[string]interface{}{
"policy": `length = 20
rule "charset" {
charset="abcdefghij"
}`,
}),
storage: new(logical.InmemStorage),
expectedResp: nil,
expectErr: true,
expectedStore: map[string]*logical.StorageEntry{},
},
"missing policy": {
inputData: passwordPoliciesFieldData(map[string]interface{}{
"name": "testpolicy",
}),
storage: new(logical.InmemStorage),
expectedResp: nil,
expectErr: true,
expectedStore: map[string]*logical.StorageEntry{},
},
"garbage policy": {
inputData: passwordPoliciesFieldData(map[string]interface{}{
"name": "testpolicy",
"policy": "hasdukfhiuashdfoiasjdf",
}),
storage: new(logical.InmemStorage),
expectedResp: nil,
expectErr: true,
expectedStore: map[string]*logical.StorageEntry{},
},
"storage failure": {
inputData: passwordPoliciesFieldData(map[string]interface{}{
"name": "testpolicy",
"policy": "length = 20\n" +
"rule \"charset\" {\n" +
" charset=\"abcdefghij\"\n" +
"}",
}),
storage: new(logical.InmemStorage).FailPut(true),
expectedResp: nil,
expectErr: true,
expectedStore: map[string]*logical.StorageEntry{},
},
"impossible policy": {
inputData: passwordPoliciesFieldData(map[string]interface{}{
"name": "testpolicy",
"policy": "length = 20\n" +
"rule \"charset\" {\n" +
" charset=\"a\"\n" +
" min-chars = 30\n" +
"}",
}),
storage: new(logical.InmemStorage),
expectedResp: nil,
expectErr: true,
expectedStore: map[string]*logical.StorageEntry{},
},
"not base64 encoded": {
inputData: passwordPoliciesFieldData(map[string]interface{}{
"name": "testpolicy",
"policy": "length = 20\n" +
"rule \"charset\" {\n" +
" charset=\"abcdefghij\"\n" +
"}",
}),
storage: new(logical.InmemStorage),
expectedResp: &logical.Response{
Data: map[string]interface{}{
logical.HTTPContentType: "application/json",
logical.HTTPStatusCode: http.StatusNoContent,
},
},
expectErr: false,
expectedStore: makeStorageMap(storageEntry(t, "testpolicy", "length = 20\n"+
"rule \"charset\" {\n"+
" charset=\"abcdefghij\"\n"+
"}")),
},
"base64 encoded": {
inputData: passwordPoliciesFieldData(map[string]interface{}{
"name": "testpolicy",
"policy": base64Encode(
"length = 20\n" +
"rule \"charset\" {\n" +
" charset=\"abcdefghij\"\n" +
"}"),
}),
storage: new(logical.InmemStorage),
expectedResp: &logical.Response{
Data: map[string]interface{}{
logical.HTTPContentType: "application/json",
logical.HTTPStatusCode: http.StatusNoContent,
},
},
expectErr: false,
expectedStore: makeStorageMap(storageEntry(t, "testpolicy",
"length = 20\n"+
"rule \"charset\" {\n"+
" charset=\"abcdefghij\"\n"+
"}")),
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
req := &logical.Request{
Storage: test.storage,
}
b := &SystemBackend{}
actualResp, err := b.handlePoliciesPasswordSet(ctx, req, test.inputData)
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
if !reflect.DeepEqual(actualResp, test.expectedResp) {
t.Fatalf("Actual response: %#v\nExpected response: %#v", actualResp, test.expectedResp)
}
actualStore := LogicalToMap(t, ctx, test.storage)
if !reflect.DeepEqual(actualStore, test.expectedStore) {
t.Fatalf("Actual: %#v\nActual: %#v", dereferenceMap(actualStore), dereferenceMap(test.expectedStore))
}
})
}
}
func TestHandlePoliciesPasswordGet(t *testing.T) {
type testCase struct {
inputData *framework.FieldData
storage *logical.InmemStorage
expectedResp *logical.Response
expectErr bool
expectedStore map[string]*logical.StorageEntry
}
tests := map[string]testCase{
"missing policy name": {
inputData: passwordPoliciesFieldData(map[string]interface{}{}),
storage: new(logical.InmemStorage),
expectedResp: nil,
expectErr: true,
expectedStore: map[string]*logical.StorageEntry{},
},
"storage error": {
inputData: passwordPoliciesFieldData(map[string]interface{}{
"name": "testpolicy",
}),
storage: new(logical.InmemStorage).FailGet(true),
expectedResp: nil,
expectErr: true,
expectedStore: map[string]*logical.StorageEntry{},
},
"missing value": {
inputData: passwordPoliciesFieldData(map[string]interface{}{
"name": "testpolicy",
}),
storage: new(logical.InmemStorage),
expectedResp: nil,
expectErr: true,
expectedStore: map[string]*logical.StorageEntry{},
},
"good value": {
inputData: passwordPoliciesFieldData(map[string]interface{}{
"name": "testpolicy",
}),
storage: makeStorage(t, storageEntry(t, "testpolicy",
"length = 20\n"+
"rule \"charset\" {\n"+
" charset=\"abcdefghij\"\n"+
"}")),
expectedResp: &logical.Response{
Data: map[string]interface{}{
"policy": "length = 20\n" +
"rule \"charset\" {\n" +
" charset=\"abcdefghij\"\n" +
"}",
},
},
expectErr: false,
expectedStore: makeStorageMap(storageEntry(t, "testpolicy",
"length = 20\n"+
"rule \"charset\" {\n"+
" charset=\"abcdefghij\"\n"+
"}")),
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
req := &logical.Request{
Storage: test.storage,
}
b := &SystemBackend{}
actualResp, err := b.handlePoliciesPasswordGet(ctx, req, test.inputData)
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
if !reflect.DeepEqual(actualResp, test.expectedResp) {
t.Fatalf("Actual response: %#v\nExpected response: %#v", actualResp, test.expectedResp)
}
actualStore := LogicalToMap(t, ctx, test.storage)
if !reflect.DeepEqual(actualStore, test.expectedStore) {
t.Fatalf("Actual: %#v\nActual: %#v", dereferenceMap(actualStore), dereferenceMap(test.expectedStore))
}
})
}
}
func TestHandlePoliciesPasswordDelete(t *testing.T) {
type testCase struct {
inputData *framework.FieldData
storage logical.Storage
expectedResp *logical.Response
expectErr bool
expectedStore map[string]*logical.StorageEntry
}
tests := map[string]testCase{
"missing policy name": {
inputData: passwordPoliciesFieldData(map[string]interface{}{}),
storage: new(logical.InmemStorage),
expectedResp: nil,
expectErr: true,
expectedStore: map[string]*logical.StorageEntry{},
},
"storage failure": {
inputData: passwordPoliciesFieldData(map[string]interface{}{
"name": "testpolicy",
}),
storage: new(logical.InmemStorage).FailDelete(true),
expectedResp: nil,
expectErr: true,
expectedStore: map[string]*logical.StorageEntry{},
},
"successful delete": {
inputData: passwordPoliciesFieldData(map[string]interface{}{
"name": "testpolicy",
}),
storage: makeStorage(t,
&logical.StorageEntry{
Key: getPasswordPolicyKey("testpolicy"),
Value: toJson(t,
passwordPolicyConfig{
HCLPolicy: "length = 18\n" +
"rule \"charset\" {\n" +
" charset=\"ABCDEFGHIJ\"\n" +
"}",
}),
},
&logical.StorageEntry{
Key: getPasswordPolicyKey("unrelated_policy"),
Value: toJson(t,
passwordPolicyConfig{
HCLPolicy: "length = 20\n" +
"rule \"charset\" {\n" +
" charset=\"abcdefghij\"\n" +
"}",
}),
},
),
expectedResp: nil,
expectErr: false,
expectedStore: makeStorageMap(storageEntry(t, "unrelated_policy",
"length = 20\n"+
"rule \"charset\" {\n"+
" charset=\"abcdefghij\"\n"+
"}")),
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
req := &logical.Request{
Storage: test.storage,
}
b := &SystemBackend{}
actualResp, err := b.handlePoliciesPasswordDelete(ctx, req, test.inputData)
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
if !reflect.DeepEqual(actualResp, test.expectedResp) {
t.Fatalf("Actual response: %#v\nExpected response: %#v", actualResp, test.expectedResp)
}
actualStore := LogicalToMap(t, ctx, test.storage)
if !reflect.DeepEqual(actualStore, test.expectedStore) {
t.Fatalf("Actual: %#v\nExpected: %#v", dereferenceMap(actualStore), dereferenceMap(test.expectedStore))
}
})
}
}
func TestHandlePoliciesPasswordGenerate(t *testing.T) {
t.Run("errors", func(t *testing.T) {
type testCase struct {
timeout time.Duration
inputData *framework.FieldData
storage *logical.InmemStorage
expectedResp *logical.Response
expectErr bool
}
tests := map[string]testCase{
"missing policy name": {
inputData: passwordPoliciesFieldData(map[string]interface{}{}),
storage: new(logical.InmemStorage),
expectedResp: nil,
expectErr: true,
},
"storage failure": {
inputData: passwordPoliciesFieldData(map[string]interface{}{
"name": "testpolicy",
}),
storage: new(logical.InmemStorage).FailGet(true),
expectedResp: nil,
expectErr: true,
},
"policy does not exist": {
inputData: passwordPoliciesFieldData(map[string]interface{}{
"name": "testpolicy",
}),
storage: new(logical.InmemStorage),
expectedResp: nil,
expectErr: true,
},
"policy improperly saved": {
inputData: passwordPoliciesFieldData(map[string]interface{}{
"name": "testpolicy",
}),
storage: makeStorage(t, storageEntry(t, "testpolicy", "badpolicy")),
expectedResp: nil,
expectErr: true,
},
"failed to generate": {
timeout: 0 * time.Second, // Timeout immediately
inputData: passwordPoliciesFieldData(map[string]interface{}{
"name": "testpolicy",
}),
storage: makeStorage(t, storageEntry(t, "testpolicy",
"length = 20\n"+
"rule \"charset\" {\n"+
" charset=\"abcdefghij\"\n"+
"}")),
expectedResp: nil,
expectErr: true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), test.timeout)
defer cancel()
req := &logical.Request{
Storage: test.storage,
}
b := &SystemBackend{}
actualResp, err := b.handlePoliciesPasswordGenerate(ctx, req, test.inputData)
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
if !reflect.DeepEqual(actualResp, test.expectedResp) {
t.Fatalf("Actual response: %#v\nExpected response: %#v", actualResp, test.expectedResp)
}
})
}
})
t.Run("success", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
policyEntry := storageEntry(t, "testpolicy",
"length = 20\n"+
"rule \"charset\" {\n"+
" charset=\"abcdefghij\"\n"+
"}")
storage := makeStorage(t, policyEntry)
inputData := passwordPoliciesFieldData(map[string]interface{}{
"name": "testpolicy",
})
expectedResp := &logical.Response{
Data: map[string]interface{}{
// Doesn't include the password as that's pulled out and compared separately
},
}
// Password assertions
expectedPassLen := 20
rules := []random.Rule{
random.CharsetRule{
Charset: []rune("abcdefghij"),
MinChars: expectedPassLen,
},
}
// Run the test a bunch of times to help ensure we don't have flaky behavior
for i := 0; i < 1000; i++ {
req := &logical.Request{
Storage: storage,
}
b := &SystemBackend{}
actualResp, err := b.handlePoliciesPasswordGenerate(ctx, req, inputData)
if err != nil {
t.Fatalf("no error expected, got: %s", err)
}
assert(t, actualResp != nil, "response is nil")
assert(t, actualResp.Data != nil, "expected data, got nil")
assertHasKey(t, actualResp.Data, "password", "password key not found in data")
assertIsString(t, actualResp.Data["password"], "password key should have a string value")
password := actualResp.Data["password"].(string)
// Delete the password so the rest of the response can be compared
delete(actualResp.Data, "password")
assert(t, reflect.DeepEqual(actualResp, expectedResp), "Actual response: %#v\nExpected response: %#v", actualResp, expectedResp)
// Check to make sure the password is correctly formatted
passwordLength := len([]rune(password))
if passwordLength != expectedPassLen {
t.Fatalf("password is %d characters but should be %d", passwordLength, expectedPassLen)
}
for _, rule := range rules {
if !rule.Pass([]rune(password)) {
t.Fatalf("password %s does not have the correct characters", password)
}
}
}
})
}
func assert(t *testing.T, pass bool, f string, vals ...interface{}) {
t.Helper()
if !pass {
t.Fatalf(f, vals...)
}
}
func assertHasKey(t *testing.T, m map[string]interface{}, key string, f string, vals ...interface{}) {
t.Helper()
_, exists := m[key]
if !exists {
t.Fatalf(f, vals...)
}
}
func assertIsString(t *testing.T, val interface{}, f string, vals ...interface{}) {
t.Helper()
_, ok := val.(string)
if !ok {
t.Fatalf(f, vals...)
}
}
func passwordPoliciesFieldData(raw map[string]interface{}) *framework.FieldData {
return &framework.FieldData{
Raw: raw,
Schema: map[string]*framework.FieldSchema{
"name": &framework.FieldSchema{
Type: framework.TypeString,
Description: "The name of the password policy.",
},
"policy": &framework.FieldSchema{
Type: framework.TypeString,
Description: "The password policy",
},
},
}
}
func base64Encode(data string) string {
return base64.StdEncoding.EncodeToString([]byte(data))
}
func toJson(t *testing.T, val interface{}) []byte {
t.Helper()
b, err := jsonutil.EncodeJSON(val)
if err != nil {
t.Fatalf("Unable to marshal to JSON: %s", err)
}
return b
}
func storageEntry(t *testing.T, key string, policy string) *logical.StorageEntry {
return &logical.StorageEntry{
Key: getPasswordPolicyKey(key),
Value: toJson(t, passwordPolicyConfig{
HCLPolicy: policy,
}),
}
}
func makeStorageMap(entries ...*logical.StorageEntry) map[string]*logical.StorageEntry {
m := map[string]*logical.StorageEntry{}
for _, entry := range entries {
m[entry.Key] = entry
}
return m
}
func dereferenceMap(store map[string]*logical.StorageEntry) map[string]interface{} {
m := map[string]interface{}{}
for k, v := range store {
m[k] = map[string]string{
"Key": v.Key,
"Value": string(v.Value),
}
}
return m
}
type walkFunc func(*logical.StorageEntry) error
// WalkLogicalStorage applies the provided walkFunc against each entry in the logical storage.
// This operates as a breadth first search.
// TODO: Figure out a place for this to live permanently. This is generic and should be in a helper package somewhere.
// At the time of writing, none of these locations work due to import cycles:
// - vault/helper/testhelpers
// - vault/helper/testhelpers/logical
// - vault/helper/testhelpers/teststorage
func WalkLogicalStorage(ctx context.Context, store logical.Storage, walker walkFunc) (err error) {
if store == nil {
return fmt.Errorf("no storage provided")
}
if walker == nil {
return fmt.Errorf("no walk function provided")
}
keys, err := store.List(ctx, "")
if err != nil {
return fmt.Errorf("unable to list root keys: %w", err)
}
// Non-recursive breadth-first search through all keys
for i := 0; i < len(keys); i++ {
key := keys[i]
entry, err := store.Get(ctx, key)
if err != nil {
return fmt.Errorf("unable to retrieve key at [%s]: %w", key, err)
}
if entry != nil {
err = walker(entry)
if err != nil {
return err
}
}
if strings.HasSuffix(key, "/") {
// Directory
subkeys, err := store.List(ctx, key)
if err != nil {
return fmt.Errorf("unable to list keys at [%s]: %w", key, err)
}
// Append the sub-keys to the keys slice so it searches into the sub-directory
for _, subkey := range subkeys {
// Avoids infinite loop if the subkey is empty which then repeats indefinitely
if subkey == "" {
continue
}
subkey = fmt.Sprintf("%s%s", key, subkey)
keys = append(keys, subkey)
}
}
}
return nil
}
// LogicalToMap retrieves all entries in the store and returns them as a map of key -> StorageEntry
func LogicalToMap(t *testing.T, ctx context.Context, store logical.Storage) (data map[string]*logical.StorageEntry) {
data = map[string]*logical.StorageEntry{}
f := func(entry *logical.StorageEntry) error {
data[entry.Key] = entry
return nil
}
err := WalkLogicalStorage(ctx, store, f)
if err != nil {
t.Fatalf("Unable to walk the storage: %s", err)
}
return data
}
// Ensure the WalkLogicalStorage function works
func TestWalkLogicalStorage(t *testing.T) {
type testCase struct {
entries []*logical.StorageEntry
}
tests := map[string]testCase{
"no entries": {
entries: []*logical.StorageEntry{},
},
"one entry": {
entries: []*logical.StorageEntry{
{
Key: "root",
},
},
},
"many entries": {
entries: []*logical.StorageEntry{
// Alphabetical, breadth-first
{Key: "bar"},
{Key: "foo"},
{Key: "bar/sub-bar1"},
{Key: "bar/sub-bar2"},
{Key: "foo/sub-foo1"},
{Key: "foo/sub-foo2"},
{Key: "foo/sub-foo3"},
{Key: "bar/sub-bar1/sub-sub-bar1"},
{Key: "bar/sub-bar1/sub-sub-bar2"},
{Key: "bar/sub-bar2/sub-sub-bar1"},
{Key: "foo/sub-foo1/sub-sub-foo1"},
{Key: "foo/sub-foo2/sub-sub-foo1"},
{Key: "foo/sub-foo3/sub-sub-foo1"},
{Key: "foo/sub-foo3/sub-sub-foo2"},
},
},
"sub key without root key": {
entries: []*logical.StorageEntry{
{Key: "foo/bar/baz"},
},
},
"key with trailing slash": {
entries: []*logical.StorageEntry{
{Key: "foo/"},
},
},
"double slash": {
entries: []*logical.StorageEntry{
{Key: "foo//"},
{Key: "foo//bar"},
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
store := makeStorage(t, test.entries...)
actualEntries := []*logical.StorageEntry{}
f := func(entry *logical.StorageEntry) error {
actualEntries = append(actualEntries, entry)
return nil
}
err := WalkLogicalStorage(ctx, store, f)
if err != nil {
t.Fatalf("Failed to walk storage: %s", err)
}
if !reflect.DeepEqual(actualEntries, test.entries) {
t.Fatalf("Actual: %#v\nExpected: %#v", actualEntries, test.entries)
}
})
}
}
func makeStorage(t *testing.T, entries ...*logical.StorageEntry) *logical.InmemStorage {
t.Helper()
ctx := context.Background()
store := new(logical.InmemStorage)
for _, entry := range entries {
err := store.Put(ctx, entry)
if err != nil {
t.Fatalf("Unable to load test storage: %s", err)
}
}
return store
}