mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
dynamic host volumes: ACL policies (#24356)
This changeset implements the ACLs required for dynamic host volumes RPCs: * `host-volume-write` is a coarse-grained policy that implies all operations. * `host-volume-register` is the highest fine-grained privilege because it potentially bypasses quotas. * `host-volume-create` is implicitly granted by `host-volume-register` * `host-volume-delete` is implicitly granted only by `host-volume-write` * `host-volume-read` is implicitly granted by `policy = "read"`, These are namespaced operations, so the testing here is predominantly around parsing and granting of implicit capabilities rather than the well-tested `AllowNamespaceOperation` method. This changeset does not include any changes to the `host_volumes` policy which we'll need for claiming volumes on job submit. That'll be covered in a later PR. Ref: https://hashicorp.atlassian.net/browse/NET-11549
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user