diff --git a/acl/acl_test.go b/acl/acl_test.go index cf0c4bda3f45..24ccf2b41035 100644 --- a/acl/acl_test.go +++ b/acl/acl_test.go @@ -79,10 +79,12 @@ func TestACLManagement(t *testing.T) { // Check default namespace rights must.True(t, acl.AllowNamespaceOperation("default", NamespaceCapabilityListJobs)) must.True(t, acl.AllowNamespaceOperation("default", NamespaceCapabilitySubmitJob)) + must.True(t, acl.AllowNamespaceOperation("default", NamespaceCapabilityHostVolumeCreate)) must.True(t, acl.AllowNamespace("default")) // Check non-specified namespace must.True(t, acl.AllowNamespaceOperation("foo", NamespaceCapabilityListJobs)) + must.True(t, acl.AllowNamespaceOperation("foo", NamespaceCapabilityHostVolumeCreate)) must.True(t, acl.AllowNamespace("foo")) // Check node pool rights. @@ -155,9 +157,11 @@ func TestACLMerge(t *testing.T) { // Check default namespace rights must.True(t, acl.AllowNamespaceOperation("default", NamespaceCapabilityListJobs)) must.False(t, acl.AllowNamespaceOperation("default", NamespaceCapabilitySubmitJob)) + must.False(t, acl.AllowNamespaceOperation("default", NamespaceCapabilityHostVolumeRegister)) // Check non-specified namespace must.False(t, acl.AllowNamespaceOperation("foo", NamespaceCapabilityListJobs)) + must.False(t, acl.AllowNamespaceOperation("foo", NamespaceCapabilityHostVolumeCreate)) // Check rights in the node pool specified in policies. must.True(t, acl.AllowNodePoolOperation("my-pool", NodePoolCapabilityRead)) diff --git a/acl/policy.go b/acl/policy.go index c4fe9e4d673c..17a7aed2170d 100644 --- a/acl/policy.go +++ b/acl/policy.go @@ -47,6 +47,11 @@ const ( NamespaceCapabilityCSIReadVolume = "csi-read-volume" NamespaceCapabilityCSIListVolume = "csi-list-volume" NamespaceCapabilityCSIMountVolume = "csi-mount-volume" + NamespaceCapabilityHostVolumeCreate = "host-volume-create" + NamespaceCapabilityHostVolumeRegister = "host-volume-register" + NamespaceCapabilityHostVolumeRead = "host-volume-read" + NamespaceCapabilityHostVolumeWrite = "host-volume-write" + NamespaceCapabilityHostVolumeDelete = "host-volume-delete" NamespaceCapabilityListScalingPolicies = "list-scaling-policies" NamespaceCapabilityReadScalingPolicy = "read-scaling-policy" NamespaceCapabilityReadJobScaling = "read-job-scaling" @@ -207,7 +212,7 @@ func isNamespaceCapabilityValid(cap string) bool { NamespaceCapabilityReadFS, NamespaceCapabilityAllocLifecycle, NamespaceCapabilityAllocExec, NamespaceCapabilityAllocNodeExec, NamespaceCapabilityCSIReadVolume, NamespaceCapabilityCSIWriteVolume, NamespaceCapabilityCSIListVolume, NamespaceCapabilityCSIMountVolume, NamespaceCapabilityCSIRegisterPlugin, - NamespaceCapabilityListScalingPolicies, NamespaceCapabilityReadScalingPolicy, NamespaceCapabilityReadJobScaling, NamespaceCapabilityScaleJob: + NamespaceCapabilityListScalingPolicies, NamespaceCapabilityReadScalingPolicy, NamespaceCapabilityReadJobScaling, NamespaceCapabilityScaleJob, NamespaceCapabilityHostVolumeCreate, NamespaceCapabilityHostVolumeRegister, NamespaceCapabilityHostVolumeWrite, NamespaceCapabilityHostVolumeRead: return true // Separate the enterprise-only capabilities case NamespaceCapabilitySentinelOverride, NamespaceCapabilitySubmitRecommendation: @@ -241,6 +246,7 @@ func expandNamespacePolicy(policy string) []string { NamespaceCapabilityReadJobScaling, NamespaceCapabilityListScalingPolicies, NamespaceCapabilityReadScalingPolicy, + NamespaceCapabilityHostVolumeRead, } write := make([]string, len(read)) @@ -257,6 +263,7 @@ func expandNamespacePolicy(policy string) []string { NamespaceCapabilityCSIMountVolume, NamespaceCapabilityCSIWriteVolume, NamespaceCapabilitySubmitRecommendation, + NamespaceCapabilityHostVolumeCreate, }...) switch policy { @@ -278,6 +285,32 @@ func expandNamespacePolicy(policy string) []string { } } +// expandNamespaceCapabilities adds extra capabilities implied by fine-grained +// capabilities. +func expandNamespaceCapabilities(ns *NamespacePolicy) { + extraCaps := []string{} + for _, cap := range ns.Capabilities { + switch cap { + case NamespaceCapabilityHostVolumeWrite: + extraCaps = append(extraCaps, + NamespaceCapabilityHostVolumeRegister, + NamespaceCapabilityHostVolumeCreate, + NamespaceCapabilityHostVolumeDelete, + NamespaceCapabilityHostVolumeRead) + case NamespaceCapabilityHostVolumeRegister: + extraCaps = append(extraCaps, + NamespaceCapabilityHostVolumeCreate, + NamespaceCapabilityHostVolumeRead) + case NamespaceCapabilityHostVolumeCreate: + extraCaps = append(extraCaps, NamespaceCapabilityHostVolumeRead) + } + } + + // These may end up being duplicated, but they'll get deduplicated in NewACL + // when inserted into the radix tree. + ns.Capabilities = append(ns.Capabilities, extraCaps...) +} + func isNodePoolCapabilityValid(cap string) bool { switch cap { case NodePoolCapabilityDelete, NodePoolCapabilityRead, NodePoolCapabilityWrite, @@ -388,6 +421,9 @@ func Parse(rules string) (*Policy, error) { ns.Capabilities = append(ns.Capabilities, extraCap...) } + // Expand implicit capabilities + expandNamespaceCapabilities(ns) + if ns.Variables != nil { if len(ns.Variables.Paths) == 0 { return nil, fmt.Errorf("Invalid variable policy: no variable paths in namespace %s", ns.Name) diff --git a/acl/policy_test.go b/acl/policy_test.go index 117b82ba3d68..938557aa08ad 100644 --- a/acl/policy_test.go +++ b/acl/policy_test.go @@ -5,7 +5,6 @@ package acl import ( "fmt" - "strings" "testing" "github.com/hashicorp/nomad/ci" @@ -17,9 +16,9 @@ func TestParse(t *testing.T) { ci.Parallel(t) type tcase struct { - Raw string - ErrStr string - Expect *Policy + Raw string + ExpectErr string + Expect *Policy } tcases := []tcase{ { @@ -43,6 +42,7 @@ func TestParse(t *testing.T) { NamespaceCapabilityReadJobScaling, NamespaceCapabilityListScalingPolicies, NamespaceCapabilityReadScalingPolicy, + NamespaceCapabilityHostVolumeRead, }, }, }, @@ -118,6 +118,7 @@ func TestParse(t *testing.T) { NamespaceCapabilityReadJobScaling, NamespaceCapabilityListScalingPolicies, NamespaceCapabilityReadScalingPolicy, + NamespaceCapabilityHostVolumeRead, }, }, { @@ -132,6 +133,7 @@ func TestParse(t *testing.T) { NamespaceCapabilityReadJobScaling, NamespaceCapabilityListScalingPolicies, NamespaceCapabilityReadScalingPolicy, + NamespaceCapabilityHostVolumeRead, NamespaceCapabilityScaleJob, NamespaceCapabilitySubmitJob, NamespaceCapabilityDispatchJob, @@ -142,6 +144,8 @@ func TestParse(t *testing.T) { NamespaceCapabilityCSIMountVolume, NamespaceCapabilityCSIWriteVolume, NamespaceCapabilitySubmitRecommendation, + NamespaceCapabilityHostVolumeCreate, + NamespaceCapabilityHostVolumeRead, }, }, { @@ -338,6 +342,7 @@ func TestParse(t *testing.T) { NamespaceCapabilityReadJobScaling, NamespaceCapabilityListScalingPolicies, NamespaceCapabilityReadScalingPolicy, + NamespaceCapabilityHostVolumeRead, }, }, { @@ -352,6 +357,7 @@ func TestParse(t *testing.T) { NamespaceCapabilityReadJobScaling, NamespaceCapabilityListScalingPolicies, NamespaceCapabilityReadScalingPolicy, + NamespaceCapabilityHostVolumeRead, NamespaceCapabilityScaleJob, NamespaceCapabilitySubmitJob, NamespaceCapabilityDispatchJob, @@ -362,6 +368,8 @@ func TestParse(t *testing.T) { NamespaceCapabilityCSIMountVolume, NamespaceCapabilityCSIWriteVolume, NamespaceCapabilitySubmitRecommendation, + NamespaceCapabilityHostVolumeCreate, + NamespaceCapabilityHostVolumeRead, }, }, { @@ -638,6 +646,54 @@ func TestParse(t *testing.T) { }, }, }, + { + ` + namespace "default" { + capabilities = ["host-volume-register"] + } + + namespace "other" { + capabilities = ["host-volume-create"] + } + + namespace "foo" { + capabilities = ["host-volume-write"] + } + `, + "", + &Policy{ + Namespaces: []*NamespacePolicy{ + { + Name: "default", + Policy: "", + Capabilities: []string{ + NamespaceCapabilityHostVolumeRegister, + NamespaceCapabilityHostVolumeCreate, + NamespaceCapabilityHostVolumeRead, + }, + }, + { + Name: "other", + Policy: "", + Capabilities: []string{ + NamespaceCapabilityHostVolumeCreate, + NamespaceCapabilityHostVolumeRead, + }, + }, + { + Name: "foo", + Policy: "", + Capabilities: []string{ + NamespaceCapabilityHostVolumeWrite, + NamespaceCapabilityHostVolumeRegister, + NamespaceCapabilityHostVolumeCreate, + NamespaceCapabilityHostVolumeDelete, + NamespaceCapabilityHostVolumeRead, + }, + }, + }, + }, + }, { ` node_pool "pool-read-only" { @@ -878,22 +934,18 @@ func TestParse(t *testing.T) { } for idx, tc := range tcases { - t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { + t.Run(fmt.Sprintf("%02d", 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 tc.ExpectErr == "" { + must.NoError(t, err) + } else { + must.ErrorContains(t, err, tc.ExpectErr) } - if err == nil && tc.ErrStr != "" { - t.Fatalf("Missing expected err") + + if tc.Expect != nil { + tc.Expect.Raw = tc.Raw + must.Eq(t, tc.Expect, p) } - tc.Expect.Raw = tc.Raw - assert.EqualValues(t, tc.Expect, p) }) } }