1cf28996e7
ACL policies can be associated with a job so that the job's Workload Identity can have expanded access to other policy objects, including other variables. Policies set on the variables the job automatically has access to were ignored, but this includes policies with `deny` capabilities. Additionally, when resolving claims for a workload identity without any attached policies, the `ResolveClaims` method returned a `nil` ACL object, which is treated similarly to a management token. While this was safe in Nomad 1.4.x, when the workload identity token was exposed to the task via the `identity` block, this allows a user with `submit-job` capabilities to escalate their privileges. We originally implemented automatic workload access to Variables as a separate code path in the Variables RPC endpoint so that we don't have to generate on-the-fly policies that blow up the ACL policy cache. This is fairly brittle but also the behavior around wildcard paths in policies different from the rest of our ACL polices, which is hard to reason about. Add an `ACLClaim` parameter to the `AllowVariableOperation` method so that we can push all this logic into the `acl` package and the behavior can be consistent. This will allow a `deny` policy to override automatic access (and probably speed up checks of non-automatic variable access).
775 lines
19 KiB
Go
775 lines
19 KiB
Go
package acl
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/hashicorp/nomad/ci"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestCapabilitySet(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
var cs capabilitySet = make(map[string]struct{})
|
|
|
|
// Check no capabilities by default
|
|
if cs.Check(PolicyDeny) {
|
|
t.Fatalf("unexpected check")
|
|
}
|
|
|
|
// Do a set and check
|
|
cs.Set(PolicyDeny)
|
|
if !cs.Check(PolicyDeny) {
|
|
t.Fatalf("missing check")
|
|
}
|
|
|
|
// Clear and check
|
|
cs.Clear()
|
|
if cs.Check(PolicyDeny) {
|
|
t.Fatalf("unexpected check")
|
|
}
|
|
}
|
|
|
|
func TestMaxPrivilege(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
type tcase struct {
|
|
Privilege string
|
|
PrecedenceOver []string
|
|
}
|
|
tcases := []tcase{
|
|
{
|
|
PolicyDeny,
|
|
[]string{PolicyDeny, PolicyWrite, PolicyRead, ""},
|
|
},
|
|
{
|
|
PolicyWrite,
|
|
[]string{PolicyWrite, PolicyRead, ""},
|
|
},
|
|
{
|
|
PolicyRead,
|
|
[]string{PolicyRead, ""},
|
|
},
|
|
}
|
|
|
|
for idx1, tc := range tcases {
|
|
for idx2, po := range tc.PrecedenceOver {
|
|
if maxPrivilege(tc.Privilege, po) != tc.Privilege {
|
|
t.Fatalf("failed %d %d", idx1, idx2)
|
|
}
|
|
if maxPrivilege(po, tc.Privilege) != tc.Privilege {
|
|
t.Fatalf("failed %d %d", idx1, idx2)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestACLManagement(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
assert := assert.New(t)
|
|
|
|
// Create management ACL
|
|
acl, err := NewACL(true, nil)
|
|
assert.Nil(err)
|
|
|
|
// Check default namespace rights
|
|
assert.True(acl.AllowNamespaceOperation("default", NamespaceCapabilityListJobs))
|
|
assert.True(acl.AllowNamespaceOperation("default", NamespaceCapabilitySubmitJob))
|
|
assert.True(acl.AllowNamespace("default"))
|
|
|
|
// Check non-specified namespace
|
|
assert.True(acl.AllowNamespaceOperation("foo", NamespaceCapabilityListJobs))
|
|
assert.True(acl.AllowNamespace("foo"))
|
|
|
|
// Check the other simpler operations
|
|
assert.True(acl.IsManagement())
|
|
assert.True(acl.AllowAgentRead())
|
|
assert.True(acl.AllowAgentWrite())
|
|
assert.True(acl.AllowNodeRead())
|
|
assert.True(acl.AllowNodeWrite())
|
|
assert.True(acl.AllowOperatorRead())
|
|
assert.True(acl.AllowOperatorWrite())
|
|
assert.True(acl.AllowQuotaRead())
|
|
assert.True(acl.AllowQuotaWrite())
|
|
}
|
|
|
|
func TestACLMerge(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
assert := assert.New(t)
|
|
|
|
// Merge read + write policy
|
|
p1, err := Parse(readAll)
|
|
assert.Nil(err)
|
|
p2, err := Parse(writeAll)
|
|
assert.Nil(err)
|
|
acl, err := NewACL(false, []*Policy{p1, p2})
|
|
assert.Nil(err)
|
|
|
|
// Check default namespace rights
|
|
assert.True(acl.AllowNamespaceOperation("default", NamespaceCapabilityListJobs))
|
|
assert.True(acl.AllowNamespaceOperation("default", NamespaceCapabilitySubmitJob))
|
|
assert.True(acl.AllowNamespace("default"))
|
|
|
|
// Check non-specified namespace
|
|
assert.False(acl.AllowNamespaceOperation("foo", NamespaceCapabilityListJobs))
|
|
assert.False(acl.AllowNamespace("foo"))
|
|
|
|
// Check the other simpler operations
|
|
assert.False(acl.IsManagement())
|
|
assert.True(acl.AllowAgentRead())
|
|
assert.True(acl.AllowAgentWrite())
|
|
assert.True(acl.AllowNodeRead())
|
|
assert.True(acl.AllowNodeWrite())
|
|
assert.True(acl.AllowOperatorRead())
|
|
assert.True(acl.AllowOperatorWrite())
|
|
assert.True(acl.AllowQuotaRead())
|
|
assert.True(acl.AllowQuotaWrite())
|
|
|
|
// Merge read + blank
|
|
p3, err := Parse("")
|
|
assert.Nil(err)
|
|
acl, err = NewACL(false, []*Policy{p1, p3})
|
|
assert.Nil(err)
|
|
|
|
// Check default namespace rights
|
|
assert.True(acl.AllowNamespaceOperation("default", NamespaceCapabilityListJobs))
|
|
assert.False(acl.AllowNamespaceOperation("default", NamespaceCapabilitySubmitJob))
|
|
|
|
// Check non-specified namespace
|
|
assert.False(acl.AllowNamespaceOperation("foo", NamespaceCapabilityListJobs))
|
|
|
|
// Check the other simpler operations
|
|
assert.False(acl.IsManagement())
|
|
assert.True(acl.AllowAgentRead())
|
|
assert.False(acl.AllowAgentWrite())
|
|
assert.True(acl.AllowNodeRead())
|
|
assert.False(acl.AllowNodeWrite())
|
|
assert.True(acl.AllowOperatorRead())
|
|
assert.False(acl.AllowOperatorWrite())
|
|
assert.True(acl.AllowQuotaRead())
|
|
assert.False(acl.AllowQuotaWrite())
|
|
|
|
// Merge read + deny
|
|
p4, err := Parse(denyAll)
|
|
assert.Nil(err)
|
|
acl, err = NewACL(false, []*Policy{p1, p4})
|
|
assert.Nil(err)
|
|
|
|
// Check default namespace rights
|
|
assert.False(acl.AllowNamespaceOperation("default", NamespaceCapabilityListJobs))
|
|
assert.False(acl.AllowNamespaceOperation("default", NamespaceCapabilitySubmitJob))
|
|
|
|
// Check non-specified namespace
|
|
assert.False(acl.AllowNamespaceOperation("foo", NamespaceCapabilityListJobs))
|
|
|
|
// Check the other simpler operations
|
|
assert.False(acl.IsManagement())
|
|
assert.False(acl.AllowAgentRead())
|
|
assert.False(acl.AllowAgentWrite())
|
|
assert.False(acl.AllowNodeRead())
|
|
assert.False(acl.AllowNodeWrite())
|
|
assert.False(acl.AllowOperatorRead())
|
|
assert.False(acl.AllowOperatorWrite())
|
|
assert.False(acl.AllowQuotaRead())
|
|
assert.False(acl.AllowQuotaWrite())
|
|
}
|
|
|
|
var readAll = `
|
|
namespace "default" {
|
|
policy = "read"
|
|
}
|
|
agent {
|
|
policy = "read"
|
|
}
|
|
node {
|
|
policy = "read"
|
|
}
|
|
operator {
|
|
policy = "read"
|
|
}
|
|
quota {
|
|
policy = "read"
|
|
}
|
|
`
|
|
|
|
var writeAll = `
|
|
namespace "default" {
|
|
policy = "write"
|
|
}
|
|
agent {
|
|
policy = "write"
|
|
}
|
|
node {
|
|
policy = "write"
|
|
}
|
|
operator {
|
|
policy = "write"
|
|
}
|
|
quota {
|
|
policy = "write"
|
|
}
|
|
`
|
|
|
|
var denyAll = `
|
|
namespace "default" {
|
|
policy = "deny"
|
|
}
|
|
agent {
|
|
policy = "deny"
|
|
}
|
|
node {
|
|
policy = "deny"
|
|
}
|
|
operator {
|
|
policy = "deny"
|
|
}
|
|
quota {
|
|
policy = "deny"
|
|
}
|
|
`
|
|
|
|
func TestAllowNamespace(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
policy string
|
|
allow bool
|
|
namespace string
|
|
}{
|
|
{
|
|
name: "foo namespace - no capabilities",
|
|
policy: `namespace "foo" {}`,
|
|
allow: false,
|
|
namespace: "foo",
|
|
},
|
|
{
|
|
name: "foo namespace - deny policy",
|
|
policy: `namespace "foo" { policy = "deny" }`,
|
|
allow: false,
|
|
namespace: "foo",
|
|
},
|
|
{
|
|
name: "foo namespace - deny capability",
|
|
policy: `namespace "foo" { capabilities = ["deny"] }`,
|
|
allow: false,
|
|
namespace: "foo",
|
|
},
|
|
{
|
|
name: "foo namespace - with capability",
|
|
policy: `namespace "foo" { capabilities = ["list-jobs"] }`,
|
|
allow: true,
|
|
namespace: "foo",
|
|
},
|
|
{
|
|
name: "foo namespace - with policy",
|
|
policy: `namespace "foo" { policy = "read" }`,
|
|
allow: true,
|
|
namespace: "foo",
|
|
},
|
|
{
|
|
name: "wildcard namespace - no capabilities",
|
|
policy: `namespace "foo" {}`,
|
|
allow: false,
|
|
namespace: "*",
|
|
},
|
|
{
|
|
name: "wildcard namespace - deny policy",
|
|
policy: `namespace "foo" { policy = "deny" }`,
|
|
allow: false,
|
|
namespace: "*",
|
|
},
|
|
{
|
|
name: "wildcard namespace - deny capability",
|
|
policy: `namespace "foo" { capabilities = ["deny"] }`,
|
|
allow: false,
|
|
namespace: "*",
|
|
},
|
|
{
|
|
name: "wildcard namespace - with capability",
|
|
policy: `namespace "foo" { capabilities = ["list-jobs"] }`,
|
|
allow: true,
|
|
namespace: "*",
|
|
},
|
|
{
|
|
name: "wildcard namespace - with policy",
|
|
policy: `namespace "foo" { policy = "read" }`,
|
|
allow: true,
|
|
namespace: "*",
|
|
},
|
|
{
|
|
name: "wildcard namespace - no namespace rule",
|
|
policy: `agent { policy = "read" }`,
|
|
allow: false,
|
|
namespace: "*",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
policy, err := Parse(tc.policy)
|
|
require.NoError(t, err)
|
|
|
|
acl, err := NewACL(false, []*Policy{policy})
|
|
require.NoError(t, err)
|
|
|
|
got := acl.AllowNamespace(tc.namespace)
|
|
require.Equal(t, tc.allow, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWildcardNamespaceMatching(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
policy string
|
|
allow bool
|
|
namespace string
|
|
}{
|
|
{
|
|
name: "wildcard matches",
|
|
policy: `namespace "prod-api-*" { policy = "write" }`,
|
|
allow: true,
|
|
namespace: "prod-api-services",
|
|
},
|
|
{
|
|
name: "non globbed namespaces are not wildcards",
|
|
policy: `namespace "prod-api" { policy = "write" }`,
|
|
allow: false,
|
|
namespace: "prod-api-services",
|
|
},
|
|
{
|
|
name: "concrete matches take precedence",
|
|
policy: `namespace "prod-api-services" { policy = "deny" }
|
|
namespace "prod-api-*" { policy = "write" }`,
|
|
allow: false,
|
|
namespace: "prod-api-services",
|
|
},
|
|
{
|
|
name: "glob match",
|
|
policy: `namespace "prod-api-*" { policy = "deny" }
|
|
namespace "prod-api-services" { policy = "write" }`,
|
|
allow: true,
|
|
namespace: "prod-api-services",
|
|
},
|
|
{
|
|
name: "closest character match wins - suffix",
|
|
policy: `namespace "*-api-services" { policy = "deny" }
|
|
namespace "prod-api-*" { policy = "write" }`, // 4 vs 8 chars
|
|
allow: false,
|
|
namespace: "prod-api-services",
|
|
},
|
|
{
|
|
name: "closest character match wins - prefix",
|
|
policy: `namespace "prod-api-*" { policy = "write" }
|
|
namespace "*-api-services" { policy = "deny" }`, // 4 vs 8 chars
|
|
allow: false,
|
|
namespace: "prod-api-services",
|
|
},
|
|
{
|
|
name: "wildcard namespace with glob match",
|
|
policy: `namespace "prod-api-*" { policy = "deny" }
|
|
namespace "prod-api-services" { policy = "write" }`,
|
|
allow: true,
|
|
namespace: "*",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
policy, err := Parse(tc.policy)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, policy.Namespaces)
|
|
|
|
acl, err := NewACL(false, []*Policy{policy})
|
|
require.NoError(t, err)
|
|
|
|
got := acl.AllowNamespace(tc.namespace)
|
|
require.Equal(t, tc.allow, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWildcardHostVolumeMatching(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
tests := []struct {
|
|
Policy string
|
|
Allow bool
|
|
}{
|
|
{ // Wildcard matches
|
|
Policy: `host_volume "prod-api-*" { policy = "write" }`,
|
|
Allow: true,
|
|
},
|
|
{ // Non globbed volumes are not wildcards
|
|
Policy: `host_volume "prod-api" { policy = "write" }`,
|
|
Allow: false,
|
|
},
|
|
{ // Concrete matches take precedence
|
|
Policy: `host_volume "prod-api-services" { policy = "deny" }
|
|
host_volume "prod-api-*" { policy = "write" }`,
|
|
Allow: false,
|
|
},
|
|
{
|
|
Policy: `host_volume "prod-api-*" { policy = "deny" }
|
|
host_volume "prod-api-services" { policy = "write" }`,
|
|
Allow: true,
|
|
},
|
|
{ // The closest character match wins
|
|
Policy: `host_volume "*-api-services" { policy = "deny" }
|
|
host_volume "prod-api-*" { policy = "write" }`, // 4 vs 8 chars
|
|
Allow: false,
|
|
},
|
|
{
|
|
Policy: `host_volume "prod-api-*" { policy = "write" }
|
|
host_volume "*-api-services" { policy = "deny" }`, // 4 vs 8 chars
|
|
Allow: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.Policy, func(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
policy, err := Parse(tc.Policy)
|
|
assert.NoError(err)
|
|
assert.NotNil(policy.HostVolumes)
|
|
|
|
acl, err := NewACL(false, []*Policy{policy})
|
|
assert.Nil(err)
|
|
|
|
assert.Equal(tc.Allow, acl.AllowHostVolume("prod-api-services"))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestVariablesMatching(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
policy string
|
|
ns string
|
|
path string
|
|
op string
|
|
claim *ACLClaim
|
|
allow bool
|
|
}{
|
|
{
|
|
name: "concrete namespace with concrete path matches",
|
|
policy: `namespace "ns" {
|
|
variables { path "foo/bar" { capabilities = ["read"] }}}`,
|
|
ns: "ns",
|
|
path: "foo/bar",
|
|
op: "read",
|
|
allow: true,
|
|
},
|
|
{
|
|
name: "concrete namespace with concrete path matches for expanded caps",
|
|
policy: `namespace "ns" {
|
|
variables { path "foo/bar" { capabilities = ["read"] }}}`,
|
|
ns: "ns",
|
|
path: "foo/bar",
|
|
op: "list",
|
|
allow: true,
|
|
},
|
|
{
|
|
name: "concrete namespace with wildcard path matches",
|
|
policy: `namespace "ns" {
|
|
variables { path "foo/*" { capabilities = ["read"] }}}`,
|
|
ns: "ns",
|
|
path: "foo/bar",
|
|
op: "read",
|
|
allow: true,
|
|
},
|
|
{
|
|
name: "concrete namespace with non-prefix wildcard path matches",
|
|
policy: `namespace "ns" {
|
|
variables { path "*/bar" { capabilities = ["read"] }}}`,
|
|
ns: "ns",
|
|
path: "foo/bar",
|
|
op: "read",
|
|
allow: true,
|
|
},
|
|
{
|
|
name: "concrete namespace with overlapping wildcard path prefix over suffix matches",
|
|
policy: `namespace "ns" {
|
|
variables {
|
|
path "*/bar" { capabilities = ["list"] }
|
|
path "foo/*" { capabilities = ["write"] }
|
|
}}`,
|
|
ns: "ns",
|
|
path: "foo/bar",
|
|
op: "write",
|
|
allow: true,
|
|
},
|
|
{
|
|
name: "concrete namespace with overlapping wildcard path prefix over suffix denied",
|
|
policy: `namespace "ns" {
|
|
variables {
|
|
path "*/bar" { capabilities = ["list"] }
|
|
path "foo/*" { capabilities = ["write"] }
|
|
}}`,
|
|
ns: "ns",
|
|
path: "foo/bar",
|
|
op: "list",
|
|
allow: false,
|
|
},
|
|
{
|
|
name: "concrete namespace with wildcard path matches most specific only",
|
|
policy: `namespace "ns" {
|
|
variables {
|
|
path "*" { capabilities = ["read"] }
|
|
path "foo/*" { capabilities = ["read"] }
|
|
path "foo/bar" { capabilities = ["list"] }
|
|
}}`,
|
|
ns: "ns",
|
|
path: "foo/bar",
|
|
op: "read",
|
|
allow: false,
|
|
},
|
|
{
|
|
name: "concrete namespace with invalid concrete path fails",
|
|
policy: `namespace "ns" {
|
|
variables { path "bar" { capabilities = ["read"] }}}`,
|
|
ns: "ns",
|
|
path: "foo/bar",
|
|
op: "read",
|
|
allow: false,
|
|
},
|
|
{
|
|
name: "concrete namespace with invalid wildcard path fails",
|
|
policy: `namespace "ns" {
|
|
variables { path "*/foo" { capabilities = ["read"] }}}`,
|
|
ns: "ns",
|
|
path: "foo/bar",
|
|
op: "read",
|
|
allow: false,
|
|
},
|
|
{
|
|
name: "wildcard namespace with concrete path matches",
|
|
policy: `namespace "*" {
|
|
variables { path "foo/bar" { capabilities = ["read"] }}}`,
|
|
ns: "ns",
|
|
path: "foo/bar",
|
|
op: "read",
|
|
allow: true,
|
|
},
|
|
{
|
|
name: "wildcard namespace with invalid concrete path fails",
|
|
policy: `namespace "*" {
|
|
variables { path "bar" { capabilities = ["read"] }}}`,
|
|
ns: "ns",
|
|
path: "foo/bar",
|
|
op: "read",
|
|
allow: false,
|
|
},
|
|
{
|
|
name: "wildcard in user provided path fails",
|
|
policy: `namespace "ns" {
|
|
variables { path "foo/bar" { capabilities = ["read"] }}}`,
|
|
ns: "ns",
|
|
path: "*",
|
|
op: "read",
|
|
allow: false,
|
|
},
|
|
{
|
|
name: "wildcard attempt to bypass delimiter null byte fails",
|
|
policy: `namespace "ns" {
|
|
variables { path "foo/bar" { capabilities = ["read"] }}}`,
|
|
ns: "ns*",
|
|
path: "bar",
|
|
op: "read",
|
|
allow: false,
|
|
},
|
|
{
|
|
name: "wildcard with more specific denied path",
|
|
policy: `namespace "ns" {
|
|
variables {
|
|
path "*" { capabilities = ["list"] }
|
|
path "system/*" { capabilities = ["deny"] }}}`,
|
|
ns: "ns",
|
|
path: "system/not-allowed",
|
|
op: "list",
|
|
allow: false,
|
|
},
|
|
{
|
|
name: "multiple namespace with overlapping paths",
|
|
policy: `namespace "ns" {
|
|
variables {
|
|
path "*" { capabilities = ["list"] }
|
|
path "system/*" { capabilities = ["deny"] }}}
|
|
namespace "prod" {
|
|
variables {
|
|
path "*" { capabilities = ["list"]}}}`,
|
|
ns: "prod",
|
|
path: "system/is-allowed",
|
|
op: "list",
|
|
allow: true,
|
|
},
|
|
{
|
|
name: "claim with more specific policy",
|
|
policy: `namespace "ns" {
|
|
variables { path "nomad/jobs/example" { capabilities = ["deny"] }}}`,
|
|
ns: "ns",
|
|
path: "nomad/jobs/example",
|
|
op: "read",
|
|
claim: &ACLClaim{Namespace: "ns", Job: "example", Group: "foo", Task: "bar"},
|
|
allow: false,
|
|
},
|
|
{
|
|
name: "claim with less specific policy",
|
|
policy: `namespace "ns" {
|
|
variables { path "nomad/jobs" { capabilities = ["deny"] }}}`,
|
|
ns: "ns",
|
|
path: "nomad/jobs/example",
|
|
op: "read",
|
|
claim: &ACLClaim{Namespace: "ns", Job: "example", Group: "foo", Task: "bar"},
|
|
allow: true,
|
|
},
|
|
{
|
|
name: "claim with less specific wildcard policy",
|
|
policy: `namespace "ns" {
|
|
variables { path "nomad/jobs/*" { capabilities = ["deny"] }}}`,
|
|
ns: "ns",
|
|
path: "nomad/jobs/example",
|
|
op: "read",
|
|
claim: &ACLClaim{Namespace: "ns", Job: "example", Group: "foo", Task: "bar"},
|
|
allow: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
policy, err := Parse(tc.policy)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, policy.Namespaces[0].Variables)
|
|
|
|
acl, err := NewACL(false, []*Policy{policy})
|
|
require.NoError(t, err)
|
|
allowed := acl.AllowVariableOperation(tc.ns, tc.path, tc.op, tc.claim)
|
|
require.Equal(t, tc.allow, allowed)
|
|
})
|
|
}
|
|
|
|
t.Run("search over namespace", func(t *testing.T) {
|
|
policy, err := Parse(`namespace "ns" {
|
|
variables { path "foo/bar" { capabilities = ["read"] }}}`)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, policy.Namespaces[0].Variables)
|
|
|
|
acl, err := NewACL(false, []*Policy{policy})
|
|
require.NoError(t, err)
|
|
require.True(t, acl.AllowVariableSearch("ns"))
|
|
require.False(t, acl.AllowVariableSearch("no-access"))
|
|
})
|
|
|
|
}
|
|
|
|
func TestACL_matchingCapabilitySet_returnsAllMatches(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
tests := []struct {
|
|
Policy string
|
|
NS string
|
|
MatchingGlobs []string
|
|
}{
|
|
{
|
|
Policy: `namespace "production-*" { policy = "write" }`,
|
|
NS: "production-api",
|
|
MatchingGlobs: []string{"production-*"},
|
|
},
|
|
{
|
|
Policy: `namespace "prod-*" { policy = "write" }`,
|
|
NS: "production-api",
|
|
MatchingGlobs: nil,
|
|
},
|
|
{
|
|
Policy: `namespace "production-*" { policy = "write" }
|
|
namespace "production-*-api" { policy = "deny" }`,
|
|
|
|
NS: "production-admin-api",
|
|
MatchingGlobs: []string{"production-*", "production-*-api"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.Policy, func(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
policy, err := Parse(tc.Policy)
|
|
assert.NoError(err)
|
|
assert.NotNil(policy.Namespaces)
|
|
|
|
acl, err := NewACL(false, []*Policy{policy})
|
|
assert.Nil(err)
|
|
|
|
var namespaces []string
|
|
for _, cs := range findAllMatchingWildcards(acl.wildcardNamespaces, tc.NS) {
|
|
namespaces = append(namespaces, cs.name)
|
|
}
|
|
|
|
assert.Equal(tc.MatchingGlobs, namespaces)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestACL_matchingCapabilitySet_difference(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
tests := []struct {
|
|
Policy string
|
|
NS string
|
|
Difference int
|
|
}{
|
|
{
|
|
Policy: `namespace "production-*" { policy = "write" }`,
|
|
NS: "production-api",
|
|
Difference: 3,
|
|
},
|
|
{
|
|
Policy: `namespace "production-*" { policy = "write" }`,
|
|
NS: "production-admin-api",
|
|
Difference: 9,
|
|
},
|
|
{
|
|
Policy: `namespace "production-**" { policy = "write" }`,
|
|
NS: "production-admin-api",
|
|
Difference: 9,
|
|
},
|
|
{
|
|
Policy: `namespace "*" { policy = "write" }`,
|
|
NS: "production-admin-api",
|
|
Difference: 20,
|
|
},
|
|
{
|
|
Policy: `namespace "*admin*" { policy = "write" }`,
|
|
NS: "production-admin-api",
|
|
Difference: 15,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.Policy, func(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
policy, err := Parse(tc.Policy)
|
|
assert.NoError(err)
|
|
assert.NotNil(policy.Namespaces)
|
|
|
|
acl, err := NewACL(false, []*Policy{policy})
|
|
assert.Nil(err)
|
|
|
|
matches := findAllMatchingWildcards(acl.wildcardNamespaces, tc.NS)
|
|
assert.Equal(tc.Difference, matches[0].difference)
|
|
})
|
|
}
|
|
|
|
}
|