1335 lines
34 KiB
Go
1335 lines
34 KiB
Go
|
// Copyright (c) HashiCorp, Inc.
|
||
|
// SPDX-License-Identifier: MPL-2.0
|
||
|
|
||
|
package vault
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
"sync"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/hashicorp/vault/helper/namespace"
|
||
|
"github.com/hashicorp/vault/sdk/logical"
|
||
|
)
|
||
|
|
||
|
func TestACL_NewACL(t *testing.T) {
|
||
|
t.Run("root-ns", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testNewACL(t, namespace.RootNamespace)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func testNewACL(t *testing.T, ns *namespace.Namespace) {
|
||
|
ctx := namespace.ContextWithNamespace(context.Background(), ns)
|
||
|
policy := []*Policy{{Name: "root"}}
|
||
|
_, err := NewACL(ctx, policy)
|
||
|
switch ns.ID {
|
||
|
case namespace.RootNamespaceID:
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
default:
|
||
|
if err == nil {
|
||
|
t.Fatal("expected an error")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestACL_MFAMethods(t *testing.T) {
|
||
|
t.Run("root-ns", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testACLMFAMethods(t, namespace.RootNamespace)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func testACLMFAMethods(t *testing.T, ns *namespace.Namespace) {
|
||
|
mfaRules := `
|
||
|
path "secret/foo/*" {
|
||
|
mfa_methods = ["mfa_method_1", "mfa_method_2", "mfa_method_3"]
|
||
|
}
|
||
|
path "secret/exact/path" {
|
||
|
mfa_methods = ["mfa_method_4", "mfa_method_5"]
|
||
|
}
|
||
|
path "secret/split/definition" {
|
||
|
mfa_methods = ["mfa_method_6", "mfa_method_7"]
|
||
|
}
|
||
|
path "secret/split/definition" {
|
||
|
mfa_methods = ["mfa_method_7", "mfa_method_8", "mfa_method_9"]
|
||
|
}
|
||
|
`
|
||
|
|
||
|
policy, err := ParseACLPolicy(ns, mfaRules)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
ctx := namespace.ContextWithNamespace(context.Background(), ns)
|
||
|
acl, err := NewACL(ctx, []*Policy{policy})
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
request := &logical.Request{
|
||
|
Operation: logical.UpdateOperation,
|
||
|
Path: "secret/foo/testing/glob/pattern",
|
||
|
}
|
||
|
|
||
|
actual := acl.AllowOperation(ctx, request, false).MFAMethods
|
||
|
expected := []string{"mfa_method_1", "mfa_method_2", "mfa_method_3"}
|
||
|
|
||
|
if !reflect.DeepEqual(expected, actual) {
|
||
|
t.Fatalf("bad: MFA methods; expected: %#v\n actual: %#v\n", expected, actual)
|
||
|
}
|
||
|
|
||
|
request.Path = "secret/exact/path"
|
||
|
actual = acl.AllowOperation(ctx, request, false).MFAMethods
|
||
|
expected = []string{"mfa_method_4", "mfa_method_5"}
|
||
|
|
||
|
if !reflect.DeepEqual(expected, actual) {
|
||
|
t.Fatalf("bad: MFA methods; expected: %#v\n actual: %#v\n", expected, actual)
|
||
|
}
|
||
|
|
||
|
request.Path = "secret/split/definition"
|
||
|
actual = acl.AllowOperation(ctx, request, false).MFAMethods
|
||
|
expected = []string{"mfa_method_6", "mfa_method_7", "mfa_method_8", "mfa_method_9"}
|
||
|
|
||
|
if !reflect.DeepEqual(expected, actual) {
|
||
|
t.Fatalf("bad: MFA methods; expected: %#v\n actual: %#v\n", expected, actual)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestACL_Capabilities(t *testing.T) {
|
||
|
t.Run("root-ns", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
policy := []*Policy{{Name: "root"}}
|
||
|
ctx := namespace.RootContext(context.Background())
|
||
|
acl, err := NewACL(ctx, policy)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
|
||
|
actual := acl.Capabilities(ctx, "any/path")
|
||
|
expected := []string{"root"}
|
||
|
if !reflect.DeepEqual(actual, expected) {
|
||
|
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
|
||
|
}
|
||
|
testACLCapabilities(t, namespace.RootNamespace)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func testACLCapabilities(t *testing.T, ns *namespace.Namespace) {
|
||
|
// Create the root policy ACL
|
||
|
ctx := namespace.ContextWithNamespace(context.Background(), ns)
|
||
|
policy, err := ParseACLPolicy(ns, aclPolicy)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
|
||
|
acl, err := NewACL(ctx, []*Policy{policy})
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
|
||
|
actual := acl.Capabilities(ctx, "dev")
|
||
|
expected := []string{"deny"}
|
||
|
if !reflect.DeepEqual(actual, expected) {
|
||
|
t.Fatalf("bad: path: %s\ngot\n%#v\nexpected\n%#v\n", "deny", actual, expected)
|
||
|
}
|
||
|
|
||
|
actual = acl.Capabilities(ctx, "dev/")
|
||
|
expected = []string{"sudo", "read", "list", "update", "delete", "create"}
|
||
|
if !reflect.DeepEqual(actual, expected) {
|
||
|
t.Fatalf("bad: path: %s\ngot\n%#v\nexpected\n%#v\n", "dev/", actual, expected)
|
||
|
}
|
||
|
|
||
|
actual = acl.Capabilities(ctx, "stage/aws/test")
|
||
|
expected = []string{"sudo", "read", "list", "update"}
|
||
|
if !reflect.DeepEqual(actual, expected) {
|
||
|
t.Fatalf("bad: path: %s\ngot\n%#v\nexpected\n%#v\n", "stage/aws/test", actual, expected)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestACL_Root(t *testing.T) {
|
||
|
t.Run("root-ns", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testACLRoot(t, namespace.RootNamespace)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func testACLRoot(t *testing.T, ns *namespace.Namespace) {
|
||
|
// Create the root policy ACL. Always create on root namespace regardless of
|
||
|
// which namespace to ACL check on.
|
||
|
policy := []*Policy{{Name: "root"}}
|
||
|
acl, err := NewACL(namespace.RootContext(context.Background()), policy)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
|
||
|
request := new(logical.Request)
|
||
|
request.Operation = logical.UpdateOperation
|
||
|
request.Path = "sys/mount/foo"
|
||
|
ctx := namespace.ContextWithNamespace(context.Background(), ns)
|
||
|
|
||
|
authResults := acl.AllowOperation(ctx, request, false)
|
||
|
if !authResults.RootPrivs {
|
||
|
t.Fatalf("expected root")
|
||
|
}
|
||
|
if !authResults.Allowed {
|
||
|
t.Fatalf("expected permissions")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestACL_Single(t *testing.T) {
|
||
|
t.Run("root-ns", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testACLSingle(t, namespace.RootNamespace)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func testACLSingle(t *testing.T, ns *namespace.Namespace) {
|
||
|
policy, err := ParseACLPolicy(ns, aclPolicy)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
|
||
|
ctx := namespace.ContextWithNamespace(context.Background(), ns)
|
||
|
acl, err := NewACL(ctx, []*Policy{policy})
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
|
||
|
// Type of operation is not important here as we only care about checking
|
||
|
// sudo/root
|
||
|
ctx = namespace.ContextWithNamespace(context.Background(), ns)
|
||
|
request := new(logical.Request)
|
||
|
request.Operation = logical.ReadOperation
|
||
|
request.Path = "sys/mount/foo"
|
||
|
|
||
|
authResults := acl.AllowOperation(ctx, request, false)
|
||
|
if authResults.RootPrivs {
|
||
|
t.Fatalf("unexpected root")
|
||
|
}
|
||
|
|
||
|
type tcase struct {
|
||
|
op logical.Operation
|
||
|
path string
|
||
|
allowed bool
|
||
|
rootPrivs bool
|
||
|
}
|
||
|
tcases := []tcase{
|
||
|
{logical.ReadOperation, "root", false, false},
|
||
|
{logical.HelpOperation, "root", true, false},
|
||
|
|
||
|
{logical.ReadOperation, "dev/foo", true, true},
|
||
|
{logical.UpdateOperation, "dev/foo", true, true},
|
||
|
|
||
|
{logical.DeleteOperation, "stage/foo", true, false},
|
||
|
{logical.ListOperation, "stage/aws/foo", true, true},
|
||
|
{logical.UpdateOperation, "stage/aws/foo", true, true},
|
||
|
{logical.UpdateOperation, "stage/aws/policy/foo", true, true},
|
||
|
|
||
|
{logical.DeleteOperation, "prod/foo", false, false},
|
||
|
{logical.UpdateOperation, "prod/foo", false, false},
|
||
|
{logical.ReadOperation, "prod/foo", true, false},
|
||
|
{logical.ListOperation, "prod/foo", true, false},
|
||
|
{logical.ReadOperation, "prod/aws/foo", false, false},
|
||
|
|
||
|
{logical.ReadOperation, "foo/bar", true, true},
|
||
|
{logical.ListOperation, "foo/bar", false, true},
|
||
|
{logical.UpdateOperation, "foo/bar", false, true},
|
||
|
{logical.CreateOperation, "foo/bar", true, true},
|
||
|
|
||
|
{logical.ReadOperation, "baz/quux", true, false},
|
||
|
{logical.CreateOperation, "baz/quux", true, false},
|
||
|
{logical.PatchOperation, "baz/quux", true, false},
|
||
|
{logical.ListOperation, "baz/quux", false, false},
|
||
|
{logical.UpdateOperation, "baz/quux", false, false},
|
||
|
|
||
|
// Path segment wildcards
|
||
|
{logical.ReadOperation, "test/foo/bar/segment", false, false},
|
||
|
{logical.ReadOperation, "test/foo/segment", true, false},
|
||
|
{logical.ReadOperation, "test/bar/segment", true, false},
|
||
|
{logical.ReadOperation, "test/segment/at/frond", false, false},
|
||
|
{logical.ReadOperation, "test/segment/at/front", true, false},
|
||
|
{logical.ReadOperation, "test/segment/at/end/foo", true, false},
|
||
|
{logical.ReadOperation, "test/segment/at/end/foo/", false, false},
|
||
|
{logical.ReadOperation, "test/segment/at/end/v2/foo/", true, false},
|
||
|
{logical.ReadOperation, "test/segment/wildcard/at/foo/", true, false},
|
||
|
{logical.ReadOperation, "test/segment/wildcard/at/end", true, false},
|
||
|
{logical.ReadOperation, "test/segment/wildcard/at/end/", true, false},
|
||
|
|
||
|
// Path segment wildcards vs glob
|
||
|
{logical.ReadOperation, "1/2/3/4", false, false},
|
||
|
{logical.ReadOperation, "1/2/3", true, false},
|
||
|
{logical.UpdateOperation, "1/2/3", false, false},
|
||
|
{logical.UpdateOperation, "1/2/3/4", true, false},
|
||
|
{logical.CreateOperation, "1/2/3/4/5", true, false},
|
||
|
}
|
||
|
|
||
|
for _, tc := range tcases {
|
||
|
ctx := namespace.ContextWithNamespace(context.Background(), ns)
|
||
|
request := new(logical.Request)
|
||
|
request.Operation = tc.op
|
||
|
request.Path = tc.path
|
||
|
|
||
|
authResults := acl.AllowOperation(ctx, request, false)
|
||
|
if authResults.Allowed != tc.allowed {
|
||
|
t.Fatalf("bad: case %#v: %v, %v", tc, authResults.Allowed, authResults.RootPrivs)
|
||
|
}
|
||
|
if authResults.RootPrivs != tc.rootPrivs {
|
||
|
t.Fatalf("bad: case %#v: %v, %v", tc, authResults.Allowed, authResults.RootPrivs)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestACL_Layered(t *testing.T) {
|
||
|
t.Run("root-ns", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
policy1, err := ParseACLPolicy(namespace.RootNamespace, aclPolicy)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
|
||
|
policy2, err := ParseACLPolicy(namespace.RootNamespace, aclPolicy2)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
acl, err := NewACL(namespace.RootContext(context.Background()), []*Policy{policy1, policy2})
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
testLayeredACL(t, acl, namespace.RootNamespace)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func testLayeredACL(t *testing.T, acl *ACL, ns *namespace.Namespace) {
|
||
|
// Type of operation is not important here as we only care about checking
|
||
|
// sudo/root
|
||
|
ctx := namespace.ContextWithNamespace(context.Background(), ns)
|
||
|
request := new(logical.Request)
|
||
|
request.Operation = logical.ReadOperation
|
||
|
request.Path = "sys/mount/foo"
|
||
|
|
||
|
authResults := acl.AllowOperation(ctx, request, false)
|
||
|
if authResults.RootPrivs {
|
||
|
t.Fatalf("unexpected root")
|
||
|
}
|
||
|
|
||
|
type tcase struct {
|
||
|
op logical.Operation
|
||
|
path string
|
||
|
allowed bool
|
||
|
rootPrivs bool
|
||
|
}
|
||
|
tcases := []tcase{
|
||
|
{logical.ReadOperation, "root", false, false},
|
||
|
{logical.HelpOperation, "root", true, false},
|
||
|
|
||
|
{logical.ReadOperation, "dev/foo", true, true},
|
||
|
{logical.UpdateOperation, "dev/foo", true, true},
|
||
|
{logical.ReadOperation, "dev/hide/foo", false, false},
|
||
|
{logical.UpdateOperation, "dev/hide/foo", false, false},
|
||
|
|
||
|
{logical.DeleteOperation, "stage/foo", true, false},
|
||
|
{logical.ListOperation, "stage/aws/foo", true, true},
|
||
|
{logical.UpdateOperation, "stage/aws/foo", true, true},
|
||
|
{logical.UpdateOperation, "stage/aws/policy/foo", false, false},
|
||
|
|
||
|
{logical.DeleteOperation, "prod/foo", true, false},
|
||
|
{logical.UpdateOperation, "prod/foo", true, false},
|
||
|
{logical.ReadOperation, "prod/foo", true, false},
|
||
|
{logical.ListOperation, "prod/foo", true, false},
|
||
|
{logical.ReadOperation, "prod/aws/foo", false, false},
|
||
|
|
||
|
{logical.ReadOperation, "sys/status", false, false},
|
||
|
{logical.UpdateOperation, "sys/seal", true, true},
|
||
|
|
||
|
{logical.ReadOperation, "foo/bar", false, false},
|
||
|
{logical.ListOperation, "foo/bar", false, false},
|
||
|
{logical.UpdateOperation, "foo/bar", false, false},
|
||
|
{logical.CreateOperation, "foo/bar", false, false},
|
||
|
|
||
|
{logical.ReadOperation, "baz/quux", false, false},
|
||
|
{logical.ListOperation, "baz/quux", false, false},
|
||
|
{logical.UpdateOperation, "baz/quux", false, false},
|
||
|
{logical.CreateOperation, "baz/quux", false, false},
|
||
|
{logical.PatchOperation, "baz/quux", false, false},
|
||
|
}
|
||
|
|
||
|
for _, tc := range tcases {
|
||
|
ctx := namespace.ContextWithNamespace(context.Background(), ns)
|
||
|
request := new(logical.Request)
|
||
|
request.Operation = tc.op
|
||
|
request.Path = tc.path
|
||
|
|
||
|
authResults := acl.AllowOperation(ctx, request, false)
|
||
|
if authResults.Allowed != tc.allowed {
|
||
|
t.Fatalf("bad: case %#v: %v, %v", tc, authResults.Allowed, authResults.RootPrivs)
|
||
|
}
|
||
|
if authResults.RootPrivs != tc.rootPrivs {
|
||
|
t.Fatalf("bad: case %#v: %v, %v", tc, authResults.Allowed, authResults.RootPrivs)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestACL_ParseMalformedPolicy(t *testing.T) {
|
||
|
_, err := ParseACLPolicy(namespace.RootNamespace, `name{}`)
|
||
|
if err == nil {
|
||
|
t.Fatalf("expected error")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestACL_PolicyMerge(t *testing.T) {
|
||
|
t.Run("root-ns", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testACLPolicyMerge(t, namespace.RootNamespace)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func testACLPolicyMerge(t *testing.T, ns *namespace.Namespace) {
|
||
|
policy, err := ParseACLPolicy(ns, mergingPolicies)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
ctx := namespace.ContextWithNamespace(context.Background(), ns)
|
||
|
acl, err := NewACL(ctx, []*Policy{policy})
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
|
||
|
type tcase struct {
|
||
|
path string
|
||
|
minWrappingTTL *time.Duration
|
||
|
maxWrappingTTL *time.Duration
|
||
|
allowed map[string][]interface{}
|
||
|
denied map[string][]interface{}
|
||
|
required []string
|
||
|
}
|
||
|
|
||
|
createDuration := func(seconds int) *time.Duration {
|
||
|
ret := time.Duration(seconds) * time.Second
|
||
|
return &ret
|
||
|
}
|
||
|
|
||
|
tcases := []tcase{
|
||
|
{"foo/bar", nil, nil, nil, map[string][]interface{}{"zip": {}, "baz": {}}, []string{"baz"}},
|
||
|
{"hello/universe", createDuration(50), createDuration(200), map[string][]interface{}{"foo": {}, "bar": {}}, nil, []string{"foo", "bar"}},
|
||
|
{"allow/all", nil, nil, map[string][]interface{}{"*": {}, "test": {}, "test1": {"foo"}}, nil, nil},
|
||
|
{"allow/all1", nil, nil, map[string][]interface{}{"*": {}, "test": {}, "test1": {"foo"}}, nil, nil},
|
||
|
{"deny/all", nil, nil, nil, map[string][]interface{}{"*": {}, "test": {}}, nil},
|
||
|
{"deny/all1", nil, nil, nil, map[string][]interface{}{"*": {}, "test": {}}, nil},
|
||
|
{"value/merge", nil, nil, map[string][]interface{}{"test": {3, 4, 1, 2}}, map[string][]interface{}{"test": {3, 4, 1, 2}}, nil},
|
||
|
{"value/empty", nil, nil, map[string][]interface{}{"empty": {}}, map[string][]interface{}{"empty": {}}, nil},
|
||
|
}
|
||
|
|
||
|
for _, tc := range tcases {
|
||
|
policyPath := ns.Path + tc.path
|
||
|
raw, ok := acl.exactRules.Get(policyPath)
|
||
|
if !ok {
|
||
|
t.Fatalf("Could not find acl entry for path %s", policyPath)
|
||
|
}
|
||
|
|
||
|
p := raw.(*ACLPermissions)
|
||
|
if !reflect.DeepEqual(tc.allowed, p.AllowedParameters) {
|
||
|
t.Fatalf("Allowed parameters did not match, Expected: %#v, Got: %#v", tc.allowed, p.AllowedParameters)
|
||
|
}
|
||
|
if !reflect.DeepEqual(tc.denied, p.DeniedParameters) {
|
||
|
t.Fatalf("Denied parameters did not match, Expected: %#v, Got: %#v", tc.denied, p.DeniedParameters)
|
||
|
}
|
||
|
if !reflect.DeepEqual(tc.required, p.RequiredParameters) {
|
||
|
t.Fatalf("Required parameters did not match, Expected: %#v, Got: %#v", tc.required, p.RequiredParameters)
|
||
|
}
|
||
|
if tc.minWrappingTTL != nil && *tc.minWrappingTTL != p.MinWrappingTTL {
|
||
|
t.Fatalf("Min wrapping TTL did not match, Expected: %#v, Got: %#v", tc.minWrappingTTL, p.MinWrappingTTL)
|
||
|
}
|
||
|
if tc.minWrappingTTL != nil && *tc.maxWrappingTTL != p.MaxWrappingTTL {
|
||
|
t.Fatalf("Max wrapping TTL did not match, Expected: %#v, Got: %#v", tc.maxWrappingTTL, p.MaxWrappingTTL)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestACL_AllowOperation(t *testing.T) {
|
||
|
t.Run("root-ns", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testACLAllowOperation(t, namespace.RootNamespace)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func testACLAllowOperation(t *testing.T, ns *namespace.Namespace) {
|
||
|
policy, err := ParseACLPolicy(ns, permissionsPolicy)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
ctx := namespace.ContextWithNamespace(context.Background(), ns)
|
||
|
acl, err := NewACL(ctx, []*Policy{policy})
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
toperations := []logical.Operation{
|
||
|
logical.UpdateOperation,
|
||
|
logical.CreateOperation,
|
||
|
}
|
||
|
type tcase struct {
|
||
|
path string
|
||
|
wrappingTTL *time.Duration
|
||
|
parameters []string
|
||
|
allowed bool
|
||
|
}
|
||
|
|
||
|
createDuration := func(seconds int) *time.Duration {
|
||
|
ret := time.Duration(seconds) * time.Second
|
||
|
return &ret
|
||
|
}
|
||
|
|
||
|
tcases := []tcase{
|
||
|
{"dev/ops", nil, []string{"zip"}, true},
|
||
|
{"foo/bar", nil, []string{"zap"}, false},
|
||
|
{"foo/bar", nil, []string{"zip"}, false},
|
||
|
{"foo/bar", createDuration(50), []string{"zip"}, false},
|
||
|
{"foo/bar", createDuration(450), []string{"zip"}, false},
|
||
|
{"foo/bar", createDuration(350), []string{"zip"}, true},
|
||
|
{"foo/baz", nil, []string{"hello"}, false},
|
||
|
{"foo/baz", createDuration(50), []string{"hello"}, false},
|
||
|
{"foo/baz", createDuration(450), []string{"hello"}, true},
|
||
|
{"foo/baz", nil, []string{"zap"}, false},
|
||
|
{"broken/phone", nil, []string{"steve"}, false},
|
||
|
{"working/phone", nil, []string{""}, false},
|
||
|
{"working/phone", createDuration(450), []string{""}, false},
|
||
|
{"working/phone", createDuration(350), []string{""}, true},
|
||
|
{"hello/world", nil, []string{"one"}, false},
|
||
|
{"tree/fort", nil, []string{"one"}, true},
|
||
|
{"tree/fort", nil, []string{"foo"}, false},
|
||
|
{"fruit/apple", nil, []string{"pear"}, false},
|
||
|
{"fruit/apple", nil, []string{"one"}, false},
|
||
|
{"cold/weather", nil, []string{"four"}, true},
|
||
|
{"var/aws", nil, []string{"cold", "warm", "kitty"}, false},
|
||
|
{"var/req", nil, []string{"cold", "warm", "kitty"}, false},
|
||
|
{"var/req", nil, []string{"cold", "warm", "kitty", "foo"}, true},
|
||
|
}
|
||
|
|
||
|
for _, tc := range tcases {
|
||
|
request := &logical.Request{
|
||
|
Path: tc.path,
|
||
|
Data: make(map[string]interface{}),
|
||
|
}
|
||
|
|
||
|
for _, parameter := range tc.parameters {
|
||
|
request.Data[parameter] = ""
|
||
|
}
|
||
|
if tc.wrappingTTL != nil {
|
||
|
request.WrapInfo = &logical.RequestWrapInfo{
|
||
|
TTL: *tc.wrappingTTL,
|
||
|
}
|
||
|
}
|
||
|
for _, op := range toperations {
|
||
|
request.Operation = op
|
||
|
ctx := namespace.ContextWithNamespace(context.Background(), ns)
|
||
|
authResults := acl.AllowOperation(ctx, request, false)
|
||
|
if authResults.Allowed != tc.allowed {
|
||
|
t.Fatalf("bad: case %#v: %v", tc, authResults.Allowed)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestACL_ValuePermissions(t *testing.T) {
|
||
|
t.Run("root-ns", func(t *testing.T) {
|
||
|
t.Parallel()
|
||
|
testACLValuePermissions(t, namespace.RootNamespace)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func testACLValuePermissions(t *testing.T, ns *namespace.Namespace) {
|
||
|
policy, err := ParseACLPolicy(ns, valuePermissionsPolicy)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
|
||
|
ctx := namespace.ContextWithNamespace(context.Background(), ns)
|
||
|
acl, err := NewACL(ctx, []*Policy{policy})
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
|
||
|
toperations := []logical.Operation{
|
||
|
logical.UpdateOperation,
|
||
|
logical.CreateOperation,
|
||
|
}
|
||
|
type tcase struct {
|
||
|
path string
|
||
|
parameters []string
|
||
|
values []interface{}
|
||
|
allowed bool
|
||
|
}
|
||
|
|
||
|
tcases := []tcase{
|
||
|
{"dev/ops", []string{"allow"}, []interface{}{"good"}, true},
|
||
|
{"dev/ops", []string{"allow"}, []interface{}{"bad"}, false},
|
||
|
{"foo/bar", []string{"deny"}, []interface{}{"bad"}, false},
|
||
|
{"foo/bar", []string{"deny"}, []interface{}{"bad glob"}, false},
|
||
|
{"foo/bar", []string{"deny"}, []interface{}{"good"}, true},
|
||
|
{"foo/bar", []string{"allow"}, []interface{}{"good"}, true},
|
||
|
{"foo/bar", []string{"deny"}, []interface{}{nil}, true},
|
||
|
{"foo/bar", []string{"allow"}, []interface{}{nil}, true},
|
||
|
{"foo/baz", []string{"aLLow"}, []interface{}{"good"}, true},
|
||
|
{"foo/baz", []string{"deny"}, []interface{}{"bad"}, false},
|
||
|
{"foo/baz", []string{"deny"}, []interface{}{"good"}, false},
|
||
|
{"foo/baz", []string{"allow", "deny"}, []interface{}{"good", "bad"}, false},
|
||
|
{"foo/baz", []string{"deny", "allow"}, []interface{}{"good", "bad"}, false},
|
||
|
{"foo/baz", []string{"deNy", "allow"}, []interface{}{"bad", "good"}, false},
|
||
|
{"foo/baz", []string{"aLLow"}, []interface{}{"bad"}, false},
|
||
|
{"foo/baz", []string{"Neither"}, []interface{}{"bad"}, false},
|
||
|
{"foo/baz", []string{"allow"}, []interface{}{nil}, false},
|
||
|
{"fizz/buzz", []string{"allow_multi"}, []interface{}{"good"}, true},
|
||
|
{"fizz/buzz", []string{"allow_multi"}, []interface{}{"good1"}, true},
|
||
|
{"fizz/buzz", []string{"allow_multi"}, []interface{}{"good2"}, true},
|
||
|
{"fizz/buzz", []string{"allow_multi"}, []interface{}{"glob good2"}, false},
|
||
|
{"fizz/buzz", []string{"allow_multi"}, []interface{}{"glob good3"}, true},
|
||
|
{"fizz/buzz", []string{"allow_multi"}, []interface{}{"bad"}, false},
|
||
|
{"fizz/buzz", []string{"allow_multi"}, []interface{}{"bad"}, false},
|
||
|
{"fizz/buzz", []string{"allow_multi", "allow"}, []interface{}{"good1", "good"}, true},
|
||
|
{"fizz/buzz", []string{"deny_multi"}, []interface{}{"bad2"}, false},
|
||
|
{"fizz/buzz", []string{"deny_multi", "allow_multi"}, []interface{}{"good", "good2"}, false},
|
||
|
// {"test/types", []string{"array"}, []interface{}{[1]string{"good"}}, true},
|
||
|
{"test/types", []string{"map"}, []interface{}{map[string]interface{}{"good": "one"}}, true},
|
||
|
{"test/types", []string{"map"}, []interface{}{map[string]interface{}{"bad": "one"}}, false},
|
||
|
{"test/types", []string{"int"}, []interface{}{1}, true},
|
||
|
{"test/types", []string{"int"}, []interface{}{3}, false},
|
||
|
{"test/types", []string{"bool"}, []interface{}{false}, true},
|
||
|
{"test/types", []string{"bool"}, []interface{}{true}, false},
|
||
|
{"test/star", []string{"anything"}, []interface{}{true}, true},
|
||
|
{"test/star", []string{"foo"}, []interface{}{true}, true},
|
||
|
{"test/star", []string{"bar"}, []interface{}{false}, true},
|
||
|
{"test/star", []string{"bar"}, []interface{}{true}, false},
|
||
|
}
|
||
|
|
||
|
for _, tc := range tcases {
|
||
|
request := &logical.Request{
|
||
|
Path: tc.path,
|
||
|
Data: make(map[string]interface{}),
|
||
|
}
|
||
|
ctx := namespace.ContextWithNamespace(context.Background(), ns)
|
||
|
|
||
|
for i, parameter := range tc.parameters {
|
||
|
request.Data[parameter] = tc.values[i]
|
||
|
}
|
||
|
for _, op := range toperations {
|
||
|
request.Operation = op
|
||
|
authResults := acl.AllowOperation(ctx, request, false)
|
||
|
if authResults.Allowed != tc.allowed {
|
||
|
t.Fatalf("bad: case %#v: %v", tc, authResults.Allowed)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestACL_SegmentWildcardPriority(t *testing.T) {
|
||
|
ns := namespace.RootNamespace
|
||
|
ctx := namespace.ContextWithNamespace(context.Background(), ns)
|
||
|
type poltest struct {
|
||
|
policy string
|
||
|
path string
|
||
|
}
|
||
|
|
||
|
// These test cases should each have a read rule and an update rule, where
|
||
|
// the update rule wins out due to being more specific.
|
||
|
poltests := []poltest{
|
||
|
{
|
||
|
// Verify edge conditions. Here '*' is more specific both because
|
||
|
// of first wildcard position (0 vs -1/infinity) and #wildcards.
|
||
|
`
|
||
|
path "+/*" { capabilities = ["read"] }
|
||
|
path "*" { capabilities = ["update"] }
|
||
|
`,
|
||
|
"foo/bar/bar/baz",
|
||
|
},
|
||
|
{
|
||
|
// Verify edge conditions. Here '+/*' is less specific because of
|
||
|
// first wildcard position.
|
||
|
`
|
||
|
path "+/*" { capabilities = ["read"] }
|
||
|
path "foo/+/*" { capabilities = ["update"] }
|
||
|
`,
|
||
|
"foo/bar/bar/baz",
|
||
|
},
|
||
|
{
|
||
|
// Verify that more wildcard segments is lower priority.
|
||
|
`
|
||
|
path "foo/+/+/*" { capabilities = ["read"] }
|
||
|
path "foo/+/bar/baz" { capabilities = ["update"] }
|
||
|
`,
|
||
|
"foo/bar/bar/baz",
|
||
|
},
|
||
|
{
|
||
|
// Verify that more wildcard segments is lower priority.
|
||
|
`
|
||
|
path "foo/+/+/baz" { capabilities = ["read"] }
|
||
|
path "foo/+/bar/baz" { capabilities = ["update"] }
|
||
|
`,
|
||
|
"foo/bar/bar/baz",
|
||
|
},
|
||
|
{
|
||
|
// Verify that first wildcard position is lower priority.
|
||
|
// '(' is used here because it is lexicographically smaller than "+"
|
||
|
`
|
||
|
path "foo/+/(ar/baz" { capabilities = ["read"] }
|
||
|
path "foo/(ar/+/baz" { capabilities = ["update"] }
|
||
|
`,
|
||
|
"foo/(ar/(ar/baz",
|
||
|
},
|
||
|
|
||
|
{
|
||
|
// Verify that a glob has lower priority, even if the prefix is the
|
||
|
// same otherwise.
|
||
|
`
|
||
|
path "foo/bar/+/baz*" { capabilities = ["read"] }
|
||
|
path "foo/bar/+/baz" { capabilities = ["update"] }
|
||
|
`,
|
||
|
"foo/bar/bar/baz",
|
||
|
},
|
||
|
{
|
||
|
// Verify that a shorter prefix has lower priority.
|
||
|
`
|
||
|
path "foo/bar/+/b*" { capabilities = ["read"] }
|
||
|
path "foo/bar/+/ba*" { capabilities = ["update"] }
|
||
|
`,
|
||
|
"foo/bar/bar/baz",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for i, pt := range poltests {
|
||
|
policy, err := ParseACLPolicy(ns, pt.policy)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
|
||
|
acl, err := NewACL(ctx, []*Policy{policy})
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
|
||
|
request := new(logical.Request)
|
||
|
request.Path = pt.path
|
||
|
|
||
|
request.Operation = logical.UpdateOperation
|
||
|
authResults := acl.AllowOperation(ctx, request, false)
|
||
|
if !authResults.Allowed {
|
||
|
t.Fatalf("bad: case %d %#v: %v", i, pt, authResults.Allowed)
|
||
|
}
|
||
|
|
||
|
request.Operation = logical.ReadOperation
|
||
|
authResults = acl.AllowOperation(ctx, request, false)
|
||
|
if authResults.Allowed {
|
||
|
t.Fatalf("bad: case %d %#v: %v", i, pt, authResults.Allowed)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestACL_SegmentWildcardPriority_BareMount(t *testing.T) {
|
||
|
ns := namespace.RootNamespace
|
||
|
ctx := namespace.ContextWithNamespace(context.Background(), ns)
|
||
|
type poltest struct {
|
||
|
policy string
|
||
|
mountpath string
|
||
|
hasperms bool
|
||
|
}
|
||
|
// These test cases should have one or more rules and a mount prefix.
|
||
|
// hasperms should be true if there are non-deny perms that apply
|
||
|
// to the mount prefix or something below it.
|
||
|
poltests := []poltest{
|
||
|
{
|
||
|
`path "+" { capabilities = ["read"] }`,
|
||
|
"foo/",
|
||
|
true,
|
||
|
},
|
||
|
{
|
||
|
`path "+/*" { capabilities = ["read"] }`,
|
||
|
"foo/",
|
||
|
true,
|
||
|
},
|
||
|
{
|
||
|
`path "foo/+/+/*" { capabilities = ["read"] }`,
|
||
|
"foo/",
|
||
|
true,
|
||
|
},
|
||
|
{
|
||
|
`path "foo/+/+/*" { capabilities = ["read"] }`,
|
||
|
"foo/bar/",
|
||
|
true,
|
||
|
},
|
||
|
{
|
||
|
`path "foo/+/+/*" { capabilities = ["read"] }`,
|
||
|
"foo/bar/bar/",
|
||
|
true,
|
||
|
},
|
||
|
{
|
||
|
`path "foo/+/+/*" { capabilities = ["read"] }`,
|
||
|
"foo/bar/bar/baz/",
|
||
|
true,
|
||
|
},
|
||
|
{
|
||
|
`path "foo/+/+/baz" { capabilities = ["read"] }`,
|
||
|
"foo/bar/bar/baz/",
|
||
|
true,
|
||
|
},
|
||
|
{
|
||
|
`path "foo/+/bar/baz" { capabilities = ["read"] }`,
|
||
|
"foo/bar/bar/baz/",
|
||
|
true,
|
||
|
},
|
||
|
{
|
||
|
`path "foo/bar/+/baz*" { capabilities = ["read"] }`,
|
||
|
"foo/bar/bar/baz/",
|
||
|
true,
|
||
|
},
|
||
|
{
|
||
|
`path "foo/bar/+/b*" { capabilities = ["read"] }`,
|
||
|
"foo/bar/bar/baz/",
|
||
|
true,
|
||
|
},
|
||
|
{
|
||
|
`path "foo/+" { capabilities = ["read"] }`,
|
||
|
"foo/",
|
||
|
true,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for i, pt := range poltests {
|
||
|
policy, err := ParseACLPolicy(ns, pt.policy)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
|
||
|
acl, err := NewACL(ctx, []*Policy{policy})
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
|
||
|
hasperms := nil != acl.CheckAllowedFromNonExactPaths(pt.mountpath, true)
|
||
|
if hasperms != pt.hasperms {
|
||
|
t.Fatalf("bad: case %d: %#v", i, pt)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// NOTE: this test doesn't catch any races ATM
|
||
|
func TestACL_CreationRace(t *testing.T) {
|
||
|
policy, err := ParseACLPolicy(namespace.RootNamespace, valuePermissionsPolicy)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
|
||
|
var wg sync.WaitGroup
|
||
|
errs := make(chan error)
|
||
|
stopTime := time.Now().Add(20 * time.Second)
|
||
|
|
||
|
for i := 0; i < 50; i++ {
|
||
|
wg.Add(1)
|
||
|
go func(i int) {
|
||
|
defer wg.Done()
|
||
|
for {
|
||
|
if time.Now().After(stopTime) {
|
||
|
return
|
||
|
}
|
||
|
_, err := NewACL(namespace.RootContext(context.Background()), []*Policy{policy})
|
||
|
if err != nil {
|
||
|
errs <- fmt.Errorf("goroutine %d: %w", i, err)
|
||
|
}
|
||
|
}
|
||
|
}(i)
|
||
|
}
|
||
|
|
||
|
go func() {
|
||
|
wg.Wait()
|
||
|
close(errs)
|
||
|
}()
|
||
|
|
||
|
for err := range errs {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestACLGrantingPolicies(t *testing.T) {
|
||
|
ns := namespace.RootNamespace
|
||
|
policy, err := ParseACLPolicy(ns, grantingTestPolicy)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
merged, err := ParseACLPolicy(ns, grantingTestPolicyMerged)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
ctx := namespace.ContextWithNamespace(context.Background(), ns)
|
||
|
|
||
|
type tcase struct {
|
||
|
path string
|
||
|
op logical.Operation
|
||
|
policies []*Policy
|
||
|
expected []logical.PolicyInfo
|
||
|
allowed bool
|
||
|
}
|
||
|
|
||
|
policyInfo := logical.PolicyInfo{
|
||
|
Name: "granting_policy",
|
||
|
NamespaceId: "root",
|
||
|
NamespacePath: "",
|
||
|
Type: "acl",
|
||
|
}
|
||
|
mergedInfo := logical.PolicyInfo{
|
||
|
Name: "granting_policy_merged",
|
||
|
NamespaceId: "root",
|
||
|
NamespacePath: "",
|
||
|
Type: "acl",
|
||
|
}
|
||
|
|
||
|
tcases := []tcase{
|
||
|
{"kv/foo", logical.ReadOperation, []*Policy{policy}, []logical.PolicyInfo{policyInfo}, true},
|
||
|
{"kv/foo", logical.UpdateOperation, []*Policy{policy}, []logical.PolicyInfo{policyInfo}, true},
|
||
|
{"kv/bad", logical.ReadOperation, []*Policy{policy}, nil, false},
|
||
|
{"kv/deny", logical.ReadOperation, []*Policy{policy}, nil, false},
|
||
|
{"kv/path/foo", logical.ReadOperation, []*Policy{policy}, []logical.PolicyInfo{policyInfo}, true},
|
||
|
{"kv/path/longer", logical.ReadOperation, []*Policy{policy}, []logical.PolicyInfo{policyInfo}, true},
|
||
|
{"kv/foo", logical.ReadOperation, []*Policy{policy, merged}, []logical.PolicyInfo{policyInfo, mergedInfo}, true},
|
||
|
{"kv/path/longer3", logical.ReadOperation, []*Policy{policy, merged}, []logical.PolicyInfo{mergedInfo}, true},
|
||
|
{"kv/bar", logical.ReadOperation, []*Policy{policy, merged}, []logical.PolicyInfo{mergedInfo}, true},
|
||
|
{"kv/deny", logical.ReadOperation, []*Policy{policy, merged}, nil, false},
|
||
|
{"kv/path/longer", logical.UpdateOperation, []*Policy{policy, merged}, []logical.PolicyInfo{policyInfo}, true},
|
||
|
{"kv/path/foo", logical.ReadOperation, []*Policy{policy, merged}, []logical.PolicyInfo{policyInfo, mergedInfo}, true},
|
||
|
}
|
||
|
|
||
|
for _, tc := range tcases {
|
||
|
request := &logical.Request{
|
||
|
Path: tc.path,
|
||
|
Operation: tc.op,
|
||
|
}
|
||
|
|
||
|
acl, err := NewACL(ctx, tc.policies)
|
||
|
if err != nil {
|
||
|
t.Fatalf("err: %v", err)
|
||
|
}
|
||
|
|
||
|
authResults := acl.AllowOperation(ctx, request, false)
|
||
|
if authResults.Allowed != tc.allowed {
|
||
|
t.Fatalf("bad: case %#v: %v", tc, authResults.Allowed)
|
||
|
}
|
||
|
if !reflect.DeepEqual(authResults.GrantingPolicies, tc.expected) {
|
||
|
t.Fatalf("bad: case %#v: got\n%#v\nexpected\n%#v\n", tc, authResults.GrantingPolicies, tc.expected)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var grantingTestPolicy = `
|
||
|
name = "granting_policy"
|
||
|
path "kv/foo" {
|
||
|
capabilities = ["update", "read"]
|
||
|
}
|
||
|
|
||
|
path "kv/path/*" {
|
||
|
capabilities = ["read"]
|
||
|
}
|
||
|
|
||
|
path "kv/path/longer" {
|
||
|
capabilities = ["update", "read"]
|
||
|
}
|
||
|
|
||
|
path "kv/path/longer2" {
|
||
|
capabilities = ["update"]
|
||
|
}
|
||
|
|
||
|
path "kv/deny" {
|
||
|
capabilities = ["deny"]
|
||
|
}
|
||
|
|
||
|
path "ns1/kv/foo" {
|
||
|
capabilities = ["update", "read"]
|
||
|
}
|
||
|
`
|
||
|
|
||
|
var grantingTestPolicyMerged = `
|
||
|
name = "granting_policy_merged"
|
||
|
path "kv/foo" {
|
||
|
capabilities = ["update", "read"]
|
||
|
}
|
||
|
|
||
|
path "kv/bar" {
|
||
|
capabilities = ["update", "read"]
|
||
|
}
|
||
|
|
||
|
path "kv/path/*" {
|
||
|
capabilities = ["read"]
|
||
|
}
|
||
|
|
||
|
path "kv/path/longer" {
|
||
|
capabilities = ["read"]
|
||
|
}
|
||
|
|
||
|
path "kv/path/longer3" {
|
||
|
capabilities = ["read"]
|
||
|
}
|
||
|
|
||
|
path "kv/deny" {
|
||
|
capabilities = ["update"]
|
||
|
}
|
||
|
`
|
||
|
|
||
|
var tokenCreationPolicy = `
|
||
|
name = "tokenCreation"
|
||
|
path "auth/token/create*" {
|
||
|
capabilities = ["update", "create", "sudo"]
|
||
|
}
|
||
|
`
|
||
|
|
||
|
var aclPolicy = `
|
||
|
name = "DeV"
|
||
|
path "dev/*" {
|
||
|
policy = "sudo"
|
||
|
}
|
||
|
path "stage/*" {
|
||
|
policy = "write"
|
||
|
}
|
||
|
path "stage/aws/*" {
|
||
|
policy = "read"
|
||
|
capabilities = ["update", "sudo"]
|
||
|
}
|
||
|
path "stage/aws/policy/*" {
|
||
|
policy = "sudo"
|
||
|
}
|
||
|
path "prod/*" {
|
||
|
policy = "read"
|
||
|
}
|
||
|
path "prod/aws/*" {
|
||
|
policy = "deny"
|
||
|
}
|
||
|
path "sys/*" {
|
||
|
policy = "deny"
|
||
|
}
|
||
|
path "foo/bar" {
|
||
|
capabilities = ["read", "create", "sudo"]
|
||
|
}
|
||
|
path "baz/quux" {
|
||
|
capabilities = ["read", "create", "patch"]
|
||
|
}
|
||
|
path "test/+/segment" {
|
||
|
capabilities = ["read"]
|
||
|
}
|
||
|
path "+/segment/at/front" {
|
||
|
capabilities = ["read"]
|
||
|
}
|
||
|
path "test/segment/at/end/+" {
|
||
|
capabilities = ["read"]
|
||
|
}
|
||
|
path "test/segment/at/end/v2/+/" {
|
||
|
capabilities = ["read"]
|
||
|
}
|
||
|
path "test/+/wildcard/+/*" {
|
||
|
capabilities = ["read"]
|
||
|
}
|
||
|
path "test/+/wildcardglob/+/end*" {
|
||
|
capabilities = ["read"]
|
||
|
}
|
||
|
path "1/2/*" {
|
||
|
capabilities = ["create"]
|
||
|
}
|
||
|
path "1/2/+" {
|
||
|
capabilities = ["read"]
|
||
|
}
|
||
|
path "1/2/+/+" {
|
||
|
capabilities = ["update"]
|
||
|
}
|
||
|
`
|
||
|
|
||
|
var aclPolicy2 = `
|
||
|
name = "OpS"
|
||
|
path "dev/hide/*" {
|
||
|
policy = "deny"
|
||
|
}
|
||
|
path "stage/aws/policy/*" {
|
||
|
policy = "deny"
|
||
|
# This should have no effect
|
||
|
capabilities = ["read", "update", "sudo"]
|
||
|
}
|
||
|
path "prod/*" {
|
||
|
policy = "write"
|
||
|
}
|
||
|
path "sys/seal" {
|
||
|
policy = "sudo"
|
||
|
}
|
||
|
path "foo/bar" {
|
||
|
capabilities = ["deny"]
|
||
|
}
|
||
|
path "baz/quux" {
|
||
|
capabilities = ["deny"]
|
||
|
}
|
||
|
`
|
||
|
|
||
|
// test merging
|
||
|
var mergingPolicies = `
|
||
|
name = "ops"
|
||
|
path "foo/bar" {
|
||
|
policy = "write"
|
||
|
denied_parameters = {
|
||
|
"baz" = []
|
||
|
}
|
||
|
required_parameters = ["baz"]
|
||
|
}
|
||
|
path "foo/bar" {
|
||
|
policy = "write"
|
||
|
denied_parameters = {
|
||
|
"zip" = []
|
||
|
}
|
||
|
}
|
||
|
path "hello/universe" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"foo" = []
|
||
|
}
|
||
|
required_parameters = ["foo"]
|
||
|
max_wrapping_ttl = 300
|
||
|
min_wrapping_ttl = 100
|
||
|
}
|
||
|
path "hello/universe" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"bar" = []
|
||
|
}
|
||
|
required_parameters = ["bar"]
|
||
|
max_wrapping_ttl = 200
|
||
|
min_wrapping_ttl = 50
|
||
|
}
|
||
|
path "allow/all" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"test" = []
|
||
|
"test1" = ["foo"]
|
||
|
}
|
||
|
}
|
||
|
path "allow/all" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"*" = []
|
||
|
}
|
||
|
}
|
||
|
path "allow/all1" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"*" = []
|
||
|
}
|
||
|
}
|
||
|
path "allow/all1" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"test" = []
|
||
|
"test1" = ["foo"]
|
||
|
}
|
||
|
}
|
||
|
path "deny/all" {
|
||
|
policy = "write"
|
||
|
denied_parameters = {
|
||
|
"test" = []
|
||
|
}
|
||
|
}
|
||
|
path "deny/all" {
|
||
|
policy = "write"
|
||
|
denied_parameters = {
|
||
|
"*" = []
|
||
|
}
|
||
|
}
|
||
|
path "deny/all1" {
|
||
|
policy = "write"
|
||
|
denied_parameters = {
|
||
|
"*" = []
|
||
|
}
|
||
|
}
|
||
|
path "deny/all1" {
|
||
|
policy = "write"
|
||
|
denied_parameters = {
|
||
|
"test" = []
|
||
|
}
|
||
|
}
|
||
|
path "value/merge" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"test" = [1, 2]
|
||
|
}
|
||
|
denied_parameters = {
|
||
|
"test" = [1, 2]
|
||
|
}
|
||
|
}
|
||
|
path "value/merge" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"test" = [3, 4]
|
||
|
}
|
||
|
denied_parameters = {
|
||
|
"test" = [3, 4]
|
||
|
}
|
||
|
}
|
||
|
path "value/empty" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"empty" = []
|
||
|
}
|
||
|
denied_parameters = {
|
||
|
"empty" = [1]
|
||
|
}
|
||
|
}
|
||
|
path "value/empty" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"empty" = [1]
|
||
|
}
|
||
|
denied_parameters = {
|
||
|
"empty" = []
|
||
|
}
|
||
|
}
|
||
|
`
|
||
|
|
||
|
// allow operation testing
|
||
|
var permissionsPolicy = `
|
||
|
name = "dev"
|
||
|
path "dev/*" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"zip" = []
|
||
|
}
|
||
|
}
|
||
|
path "foo/bar" {
|
||
|
policy = "write"
|
||
|
denied_parameters = {
|
||
|
"zap" = []
|
||
|
}
|
||
|
min_wrapping_ttl = 300
|
||
|
max_wrapping_ttl = 400
|
||
|
}
|
||
|
path "foo/baz" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"hello" = []
|
||
|
}
|
||
|
denied_parameters = {
|
||
|
"zap" = []
|
||
|
}
|
||
|
min_wrapping_ttl = 300
|
||
|
}
|
||
|
path "working/phone" {
|
||
|
policy = "write"
|
||
|
max_wrapping_ttl = 400
|
||
|
}
|
||
|
path "broken/phone" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"steve" = []
|
||
|
}
|
||
|
denied_parameters = {
|
||
|
"steve" = []
|
||
|
}
|
||
|
}
|
||
|
path "hello/world" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"*" = []
|
||
|
}
|
||
|
denied_parameters = {
|
||
|
"*" = []
|
||
|
}
|
||
|
}
|
||
|
path "tree/fort" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"*" = []
|
||
|
}
|
||
|
denied_parameters = {
|
||
|
"foo" = []
|
||
|
}
|
||
|
}
|
||
|
path "fruit/apple" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"pear" = []
|
||
|
}
|
||
|
denied_parameters = {
|
||
|
"*" = []
|
||
|
}
|
||
|
}
|
||
|
path "cold/weather" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {}
|
||
|
denied_parameters = {}
|
||
|
}
|
||
|
path "var/aws" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"*" = []
|
||
|
}
|
||
|
denied_parameters = {
|
||
|
"soft" = []
|
||
|
"warm" = []
|
||
|
"kitty" = []
|
||
|
}
|
||
|
}
|
||
|
path "var/req" {
|
||
|
policy = "write"
|
||
|
required_parameters = ["foo"]
|
||
|
}
|
||
|
`
|
||
|
|
||
|
// allow operation testing
|
||
|
var valuePermissionsPolicy = `
|
||
|
name = "op"
|
||
|
path "dev/*" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"allow" = ["good"]
|
||
|
}
|
||
|
}
|
||
|
path "foo/bar" {
|
||
|
policy = "write"
|
||
|
denied_parameters = {
|
||
|
"deny" = ["bad*"]
|
||
|
}
|
||
|
}
|
||
|
path "foo/baz" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"ALLOW" = ["good"]
|
||
|
}
|
||
|
denied_parameters = {
|
||
|
"dEny" = ["bad"]
|
||
|
}
|
||
|
}
|
||
|
path "fizz/buzz" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"allow_multi" = ["good", "good1", "good2", "*good3"]
|
||
|
"allow" = ["good"]
|
||
|
}
|
||
|
denied_parameters = {
|
||
|
"deny_multi" = ["bad", "bad1", "bad2"]
|
||
|
}
|
||
|
}
|
||
|
path "test/types" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"map" = [{"good" = "one"}]
|
||
|
"int" = [1, 2]
|
||
|
"bool" = [false]
|
||
|
}
|
||
|
denied_parameters = {
|
||
|
}
|
||
|
}
|
||
|
path "test/star" {
|
||
|
policy = "write"
|
||
|
allowed_parameters = {
|
||
|
"*" = []
|
||
|
"foo" = []
|
||
|
"bar" = [false]
|
||
|
}
|
||
|
denied_parameters = {
|
||
|
}
|
||
|
}
|
||
|
`
|