open-nomad/acl/policy_test.go

901 lines
16 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package acl
import (
"fmt"
"strings"
"testing"
"github.com/hashicorp/nomad/ci"
"github.com/stretchr/testify/assert"
)
func TestParse(t *testing.T) {
ci.Parallel(t)
type tcase struct {
Raw string
ErrStr string
Expect *Policy
}
tcases := []tcase{
{
`
namespace "default" {
policy = "read"
}
`,
"",
&Policy{
Namespaces: []*NamespacePolicy{
{
Name: "default",
Policy: PolicyRead,
Capabilities: []string{
NamespaceCapabilityListJobs,
NamespaceCapabilityParseJob,
NamespaceCapabilityReadJob,
NamespaceCapabilityCSIListVolume,
NamespaceCapabilityCSIReadVolume,
NamespaceCapabilityReadJobScaling,
NamespaceCapabilityListScalingPolicies,
NamespaceCapabilityReadScalingPolicy,
},
},
},
},
},
{
`
namespace "default" {
policy = "read"
}
namespace "other" {
policy = "write"
}
namespace "secret" {
capabilities = ["deny", "read-logs"]
}
namespace "apps" {
variables {
path "jobs/write-does-not-imply-read-or-delete" {
capabilities = ["write"]
}
path "project/read-implies-list" {
capabilities = ["read"]
}
path "project/explicit" {
capabilities = ["read", "list", "destroy"]
}
}
}
namespace "autoscaler" {
policy = "scale"
}
host_volume "production-tls-*" {
capabilities = ["mount-readonly"]
}
host_volume "staging-tls-*" {
policy = "write"
}
node_pool "prod" {
capabilities = ["read"]
}
node_pool "dev" {
policy = "write"
}
agent {
policy = "read"
}
node {
policy = "write"
}
operator {
policy = "deny"
}
quota {
policy = "read"
}
plugin {
policy = "read"
}
`,
"",
&Policy{
Namespaces: []*NamespacePolicy{
{
Name: "default",
Policy: PolicyRead,
Capabilities: []string{
NamespaceCapabilityListJobs,
NamespaceCapabilityParseJob,
NamespaceCapabilityReadJob,
NamespaceCapabilityCSIListVolume,
NamespaceCapabilityCSIReadVolume,
NamespaceCapabilityReadJobScaling,
NamespaceCapabilityListScalingPolicies,
NamespaceCapabilityReadScalingPolicy,
},
},
{
Name: "other",
Policy: PolicyWrite,
Capabilities: []string{
NamespaceCapabilityListJobs,
NamespaceCapabilityParseJob,
NamespaceCapabilityReadJob,
NamespaceCapabilityCSIListVolume,
NamespaceCapabilityCSIReadVolume,
NamespaceCapabilityReadJobScaling,
NamespaceCapabilityListScalingPolicies,
NamespaceCapabilityReadScalingPolicy,
NamespaceCapabilityScaleJob,
NamespaceCapabilitySubmitJob,
NamespaceCapabilityDispatchJob,
NamespaceCapabilityReadLogs,
NamespaceCapabilityReadFS,
NamespaceCapabilityAllocExec,
NamespaceCapabilityAllocLifecycle,
NamespaceCapabilityCSIMountVolume,
NamespaceCapabilityCSIWriteVolume,
NamespaceCapabilitySubmitRecommendation,
},
},
{
Name: "secret",
Capabilities: []string{
NamespaceCapabilityDeny,
NamespaceCapabilityReadLogs,
},
},
{
Name: "apps",
Variables: &VariablesPolicy{
Paths: []*VariablesPathPolicy{
{
PathSpec: "jobs/write-does-not-imply-read-or-delete",
Capabilities: []string{VariablesCapabilityWrite},
},
{
PathSpec: "project/read-implies-list",
Capabilities: []string{
VariablesCapabilityRead,
VariablesCapabilityList,
},
},
{
PathSpec: "project/explicit",
Capabilities: []string{
VariablesCapabilityRead,
VariablesCapabilityList,
VariablesCapabilityDestroy,
},
},
},
},
},
{
Name: "autoscaler",
Policy: PolicyScale,
Capabilities: []string{
NamespaceCapabilityListScalingPolicies,
NamespaceCapabilityReadScalingPolicy,
NamespaceCapabilityReadJobScaling,
NamespaceCapabilityScaleJob,
},
},
},
HostVolumes: []*HostVolumePolicy{
{
Name: "production-tls-*",
Capabilities: []string{"mount-readonly"},
},
{
Name: "staging-tls-*",
Policy: "write",
Capabilities: []string{
"mount-readonly",
"mount-readwrite",
},
},
},
NodePools: []*NodePoolPolicy{
{
Name: "prod",
Capabilities: []string{"read"},
},
{
Name: "dev",
Policy: "write",
Capabilities: []string{"delete", "read", "write"},
},
},
Agent: &AgentPolicy{
Policy: PolicyRead,
},
Node: &NodePolicy{
Policy: PolicyWrite,
},
Operator: &OperatorPolicy{
Policy: PolicyDeny,
},
Quota: &QuotaPolicy{
Policy: PolicyRead,
},
Plugin: &PluginPolicy{
Policy: PolicyRead,
},
},
},
{
`
{
"namespace": [
{
"default": {
"policy": "read"
},
},
{
"other": {
"policy": "write"
},
},
{
"secret": {
"capabilities": [
"deny",
"read-logs"
]
}
},
{
"apps": {
"variables": [
{
"path": [
{
"jobs/write-does-not-imply-read-or-delete": {
"capabilities": ["write"],
},
},
{
"project/read-implies-list": {
"capabilities": ["read"],
},
},
{
"project/explicit": {
"capabilities": ["read", "list", "destroy"],
},
},
],
},
],
},
},
{
"autoscaler": {
"policy": "scale"
},
},
],
"host_volume": [
{
"production-tls-*": {
"capabilities": ["mount-readonly"]
}
},
{
"staging-tls-*": {
"policy": "write"
}
}
],
"node_pool": [
{
"prod": {
"capabilities": ["read"]
}
},
{
"dev": {
"policy": "write"
}
}
],
"agent": {
"policy": "read"
},
"node": {
"policy": "write"
},
"operator": {
"policy": "deny"
},
"quota": {
"policy": "read"
},
"plugin": {
"policy": "read"
}
}`,
"",
&Policy{
Namespaces: []*NamespacePolicy{
{
Name: "default",
Policy: PolicyRead,
Capabilities: []string{
NamespaceCapabilityListJobs,
NamespaceCapabilityParseJob,
NamespaceCapabilityReadJob,
NamespaceCapabilityCSIListVolume,
NamespaceCapabilityCSIReadVolume,
NamespaceCapabilityReadJobScaling,
NamespaceCapabilityListScalingPolicies,
NamespaceCapabilityReadScalingPolicy,
},
},
{
Name: "other",
Policy: PolicyWrite,
Capabilities: []string{
NamespaceCapabilityListJobs,
NamespaceCapabilityParseJob,
NamespaceCapabilityReadJob,
NamespaceCapabilityCSIListVolume,
NamespaceCapabilityCSIReadVolume,
NamespaceCapabilityReadJobScaling,
NamespaceCapabilityListScalingPolicies,
NamespaceCapabilityReadScalingPolicy,
NamespaceCapabilityScaleJob,
NamespaceCapabilitySubmitJob,
NamespaceCapabilityDispatchJob,
NamespaceCapabilityReadLogs,
NamespaceCapabilityReadFS,
NamespaceCapabilityAllocExec,
NamespaceCapabilityAllocLifecycle,
NamespaceCapabilityCSIMountVolume,
NamespaceCapabilityCSIWriteVolume,
NamespaceCapabilitySubmitRecommendation,
},
},
{
Name: "secret",
Capabilities: []string{
NamespaceCapabilityDeny,
NamespaceCapabilityReadLogs,
},
},
{
Name: "apps",
Variables: &VariablesPolicy{
Paths: []*VariablesPathPolicy{
{
PathSpec: "jobs/write-does-not-imply-read-or-delete",
Capabilities: []string{VariablesCapabilityWrite},
},
{
PathSpec: "project/read-implies-list",
Capabilities: []string{
VariablesCapabilityRead,
VariablesCapabilityList,
},
},
{
PathSpec: "project/explicit",
Capabilities: []string{
VariablesCapabilityRead,
VariablesCapabilityList,
VariablesCapabilityDestroy,
},
},
},
},
},
{
Name: "autoscaler",
Policy: PolicyScale,
Capabilities: []string{
NamespaceCapabilityListScalingPolicies,
NamespaceCapabilityReadScalingPolicy,
NamespaceCapabilityReadJobScaling,
NamespaceCapabilityScaleJob,
},
},
},
HostVolumes: []*HostVolumePolicy{
{
Name: "production-tls-*",
Capabilities: []string{"mount-readonly"},
},
{
Name: "staging-tls-*",
Policy: "write",
Capabilities: []string{
"mount-readonly",
"mount-readwrite",
},
},
},
NodePools: []*NodePoolPolicy{
{
Name: "prod",
Capabilities: []string{"read"},
},
{
Name: "dev",
Policy: "write",
Capabilities: []string{"delete", "read", "write"},
},
},
Agent: &AgentPolicy{
Policy: PolicyRead,
},
Node: &NodePolicy{
Policy: PolicyWrite,
},
Operator: &OperatorPolicy{
Policy: PolicyDeny,
},
Quota: &QuotaPolicy{
Policy: PolicyRead,
},
Plugin: &PluginPolicy{
Policy: PolicyRead,
},
},
},
{
`
namespace "default" {
policy = "foo"
}
`,
"Invalid namespace policy",
nil,
},
{
`
namespace {
policy = "read"
}
`,
"Invalid namespace name",
nil,
},
{
`
{
"namespace": [
{
"": {
"policy": "read"
}
}
]
}
`,
"Invalid namespace name",
nil,
},
{
`
namespace "dev" {
variables "*" {
capabilities = ["read", "write"]
}
}
`,
"Invalid variable policy: no variable paths in namespace dev",
nil,
},
{
`
namespace "dev" {
policy = "read"
variables {
path {}
path "nomad/jobs/example" {
capabilities = ["read"]
}
}
}
`,
"Invalid missing variable path in namespace",
nil,
},
{
`
{
"namespace": [
{
"dev": {
"policy": "read",
"variables": [
{
"paths": [
{
"": {
"capabilities": ["read"]
}
}
]
]
]
}
}
]
}
`,
"no variable paths in namespace dev",
nil,
},
{
`
namespace "default" {
capabilities = ["deny", "foo"]
}
`,
"Invalid namespace capability",
nil,
},
{
`namespace {}`,
"invalid acl policy",
nil,
},
{
`
agent {
policy = "foo"
}
`,
"Invalid agent policy",
nil,
},
{
`
node {
policy = "foo"
}
`,
"Invalid node policy",
nil,
},
{
`
operator {
policy = "foo"
}
`,
"Invalid operator policy",
nil,
},
{
`
quota {
policy = "foo"
}
`,
"Invalid quota policy",
nil,
},
{
`
{
"Name": "my-policy",
"Description": "This is a great policy",
"Rules": "anything"
}
`,
"Invalid policy",
nil,
},
{
`
namespace "has a space"{
policy = "read"
}
`,
"Invalid namespace name",
nil,
},
{
`
namespace "default" {
capabilities = ["sentinel-override"]
}
`,
"",
&Policy{
Namespaces: []*NamespacePolicy{
{
Name: "default",
Policy: "",
Capabilities: []string{
NamespaceCapabilitySentinelOverride,
},
},
},
},
},
{
`
node_pool "pool-read-only" {
policy = "read"
}
node_pool "pool-read-write" {
policy = "write"
}
node_pool "pool-read-upsert" {
policy = "read"
capabilities = ["write"]
}
node_pool "pool-multiple-capabilities" {
policy = "read"
capabilities = ["write", "delete"]
}
node_pool "pool-deny-policy" {
policy = "deny"
capabilities = ["write"]
}
node_pool "pool-deny-capability" {
capabilities = ["deny", "read"]
}
node_pool "pool-*" {
policy = "read"
}
`,
"",
&Policy{
NodePools: []*NodePoolPolicy{
{
Name: "pool-read-only",
Policy: PolicyRead,
Capabilities: []string{
NodePoolCapabilityRead,
},
},
{
Name: "pool-read-write",
Policy: PolicyWrite,
Capabilities: []string{
NodePoolCapabilityDelete,
NodePoolCapabilityRead,
NodePoolCapabilityWrite,
},
},
{
Name: "pool-read-upsert",
Policy: PolicyRead,
Capabilities: []string{
NodePoolCapabilityWrite,
NodePoolCapabilityRead,
},
},
{
Name: "pool-multiple-capabilities",
Policy: PolicyRead,
Capabilities: []string{
NodePoolCapabilityWrite,
NodePoolCapabilityDelete,
NodePoolCapabilityRead,
},
},
{
Name: "pool-deny-policy",
Policy: PolicyDeny,
Capabilities: []string{
NodePoolCapabilityWrite,
NodePoolCapabilityDeny,
},
},
{
Name: "pool-deny-capability",
Policy: "",
Capabilities: []string{
NodePoolCapabilityDeny,
NodePoolCapabilityRead,
},
},
{
Name: "pool-*",
Policy: PolicyRead,
Capabilities: []string{
NodePoolCapabilityRead,
},
},
},
},
},
{
`
node_pool "" {
}
`,
"Invalid node pool name",
nil,
},
{
`
node_pool "pool%" {
}
`,
"Invalid node pool name",
nil,
},
{
`
node_pool "my-pool" {
capabilities = ["read", "invalid"]
}
`,
"Invalid node pool capability",
nil,
},
{
`
node_pool {
policy = "read"
}
`,
"Invalid node pool name",
nil,
},
{
`
{
"node_pool": [
{
"": {
"policy": "read"
}
}
]
}
`,
"Invalid node pool name",
nil,
},
{
`
host_volume "production-tls-*" {
capabilities = ["mount-readonly"]
}
`,
"",
&Policy{
HostVolumes: []*HostVolumePolicy{
{
Name: "production-tls-*",
Policy: "",
Capabilities: []string{
HostVolumeCapabilityMountReadOnly,
},
},
},
},
},
{
`
host_volume "production-tls-*" {
capabilities = ["mount-readwrite"]
}
`,
"",
&Policy{
HostVolumes: []*HostVolumePolicy{
{
Name: "production-tls-*",
Policy: "",
Capabilities: []string{
HostVolumeCapabilityMountReadWrite,
},
},
},
},
},
{
`
host_volume "volume has a space" {
capabilities = ["mount-readwrite"]
}
`,
"Invalid host volume name",
nil,
},
{
`
host_volume {
policy = "read"
}
`,
"Invalid host volume name",
nil,
},
{
`
{
"host_volume": [
{
"": {
"policy": "read"
}
}
]
}
`,
"Invalid host volume name",
nil,
},
{
`
plugin {
policy = "list"
}
`,
"",
&Policy{
Plugin: &PluginPolicy{
Policy: PolicyList,
},
},
},
{
`
plugin {
policy = "reader"
}
`,
"Invalid plugin policy",
nil,
},
}
for idx, tc := range tcases {
t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) {
p, err := Parse(tc.Raw)
if err != nil {
if tc.ErrStr == "" {
t.Fatalf("Unexpected err: %v", err)
}
if !strings.Contains(err.Error(), tc.ErrStr) {
t.Fatalf("Unexpected err: %v", err)
}
return
}
if err == nil && tc.ErrStr != "" {
t.Fatalf("Missing expected err")
}
tc.Expect.Raw = tc.Raw
assert.EqualValues(t, tc.Expect, p)
})
}
}
func TestParse_BadInput(t *testing.T) {
ci.Parallel(t)
inputs := []string{
`namespace "\500" {}`,
}
for i, c := range inputs {
t.Run(fmt.Sprintf("%d: %v", i, c), func(t *testing.T) {
_, err := Parse(c)
assert.Error(t, err)
})
}
}