2017-08-04 00:41:33 +00:00
|
|
|
package acl
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2017-08-05 00:34:22 +00:00
|
|
|
"regexp"
|
2017-08-04 00:41:33 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/hcl"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2017-08-08 04:09:13 +00:00
|
|
|
// The following levels are the only valid values for the `policy = "read"` stanza.
|
|
|
|
// When policies are merged together, the most privilege is granted, except for deny
|
2020-04-20 13:28:19 +00:00
|
|
|
// which always takes precedence and supersedes.
|
2020-03-24 19:29:34 +00:00
|
|
|
PolicyDeny = "deny"
|
|
|
|
PolicyRead = "read"
|
|
|
|
PolicyList = "list"
|
|
|
|
PolicyWrite = "write"
|
|
|
|
PolicyScale = "scale"
|
2017-08-04 00:41:33 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2017-08-08 04:09:13 +00:00
|
|
|
// The following are the fine-grained capabilities that can be granted within a namespace.
|
|
|
|
// The Policy stanza is a short hand for granting several of these. When capabilities are
|
|
|
|
// combined we take the union of all capabilities. If the deny capability is present, it
|
|
|
|
// takes precedence and overwrites all other capabilities.
|
2019-05-08 11:14:24 +00:00
|
|
|
|
2020-03-22 14:21:51 +00:00
|
|
|
NamespaceCapabilityDeny = "deny"
|
|
|
|
NamespaceCapabilityListJobs = "list-jobs"
|
|
|
|
NamespaceCapabilityReadJob = "read-job"
|
|
|
|
NamespaceCapabilitySubmitJob = "submit-job"
|
|
|
|
NamespaceCapabilityDispatchJob = "dispatch-job"
|
|
|
|
NamespaceCapabilityReadLogs = "read-logs"
|
|
|
|
NamespaceCapabilityReadFS = "read-fs"
|
|
|
|
NamespaceCapabilityAllocExec = "alloc-exec"
|
|
|
|
NamespaceCapabilityAllocNodeExec = "alloc-node-exec"
|
|
|
|
NamespaceCapabilityAllocLifecycle = "alloc-lifecycle"
|
|
|
|
NamespaceCapabilitySentinelOverride = "sentinel-override"
|
|
|
|
NamespaceCapabilityCSIRegisterPlugin = "csi-register-plugin"
|
|
|
|
NamespaceCapabilityCSIWriteVolume = "csi-write-volume"
|
|
|
|
NamespaceCapabilityCSIReadVolume = "csi-read-volume"
|
|
|
|
NamespaceCapabilityCSIListVolume = "csi-list-volume"
|
|
|
|
NamespaceCapabilityCSIMountVolume = "csi-mount-volume"
|
|
|
|
NamespaceCapabilityListScalingPolicies = "list-scaling-policies"
|
|
|
|
NamespaceCapabilityReadScalingPolicy = "read-scaling-policy"
|
|
|
|
NamespaceCapabilityReadJobScaling = "read-job-scaling"
|
|
|
|
NamespaceCapabilityScaleJob = "scale-job"
|
2017-08-04 00:41:33 +00:00
|
|
|
)
|
|
|
|
|
2017-08-05 00:34:22 +00:00
|
|
|
var (
|
2018-11-08 20:09:00 +00:00
|
|
|
validNamespace = regexp.MustCompile("^[a-zA-Z0-9-*]{1,128}$")
|
2017-08-05 00:34:22 +00:00
|
|
|
)
|
|
|
|
|
2019-05-08 11:14:24 +00:00
|
|
|
const (
|
|
|
|
// The following are the fine-grained capabilities that can be granted for a volume set.
|
|
|
|
// The Policy stanza is a short hand for granting several of these. When capabilities are
|
|
|
|
// combined we take the union of all capabilities. If the deny capability is present, it
|
|
|
|
// takes precedence and overwrites all other capabilities.
|
|
|
|
|
2019-08-21 18:13:16 +00:00
|
|
|
HostVolumeCapabilityDeny = "deny"
|
|
|
|
HostVolumeCapabilityMountReadOnly = "mount-readonly"
|
|
|
|
HostVolumeCapabilityMountReadWrite = "mount-readwrite"
|
2019-05-08 11:14:24 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
validVolume = regexp.MustCompile("^[a-zA-Z0-9-*]{1,128}$")
|
|
|
|
)
|
|
|
|
|
2017-08-04 00:41:33 +00:00
|
|
|
// Policy represents a parsed HCL or JSON policy.
|
|
|
|
type Policy struct {
|
2019-05-08 11:14:24 +00:00
|
|
|
Namespaces []*NamespacePolicy `hcl:"namespace,expand"`
|
|
|
|
HostVolumes []*HostVolumePolicy `hcl:"host_volume,expand"`
|
|
|
|
Agent *AgentPolicy `hcl:"agent"`
|
|
|
|
Node *NodePolicy `hcl:"node"`
|
|
|
|
Operator *OperatorPolicy `hcl:"operator"`
|
|
|
|
Quota *QuotaPolicy `hcl:"quota"`
|
2020-03-17 21:32:39 +00:00
|
|
|
Plugin *PluginPolicy `hcl:"plugin"`
|
2019-05-08 11:14:24 +00:00
|
|
|
Raw string `hcl:"-"`
|
2017-08-04 00:41:33 +00:00
|
|
|
}
|
|
|
|
|
2017-10-17 21:15:11 +00:00
|
|
|
// IsEmpty checks to make sure that at least one policy has been set and is not
|
|
|
|
// comprised of only a raw policy.
|
|
|
|
func (p *Policy) IsEmpty() bool {
|
|
|
|
return len(p.Namespaces) == 0 &&
|
2019-05-08 11:14:24 +00:00
|
|
|
len(p.HostVolumes) == 0 &&
|
2017-10-17 21:15:11 +00:00
|
|
|
p.Agent == nil &&
|
|
|
|
p.Node == nil &&
|
|
|
|
p.Operator == nil &&
|
2020-03-17 21:32:39 +00:00
|
|
|
p.Quota == nil &&
|
|
|
|
p.Plugin == nil
|
2017-10-17 21:15:11 +00:00
|
|
|
}
|
|
|
|
|
2017-08-04 00:41:33 +00:00
|
|
|
// NamespacePolicy is the policy for a specific namespace
|
|
|
|
type NamespacePolicy struct {
|
|
|
|
Name string `hcl:",key"`
|
|
|
|
Policy string
|
|
|
|
Capabilities []string
|
|
|
|
}
|
|
|
|
|
2019-05-08 11:14:24 +00:00
|
|
|
// HostVolumePolicy is the policy for a specific named host volume
|
|
|
|
type HostVolumePolicy struct {
|
|
|
|
Name string `hcl:",key"`
|
|
|
|
Policy string
|
|
|
|
Capabilities []string
|
|
|
|
}
|
|
|
|
|
2017-08-04 00:41:33 +00:00
|
|
|
type AgentPolicy struct {
|
|
|
|
Policy string
|
|
|
|
}
|
|
|
|
|
|
|
|
type NodePolicy struct {
|
|
|
|
Policy string
|
|
|
|
}
|
|
|
|
|
|
|
|
type OperatorPolicy struct {
|
|
|
|
Policy string
|
|
|
|
}
|
|
|
|
|
2017-10-13 21:36:02 +00:00
|
|
|
type QuotaPolicy struct {
|
|
|
|
Policy string
|
|
|
|
}
|
|
|
|
|
2020-03-17 21:32:39 +00:00
|
|
|
type PluginPolicy struct {
|
|
|
|
Policy string
|
|
|
|
}
|
|
|
|
|
2017-08-04 00:41:33 +00:00
|
|
|
// isPolicyValid makes sure the given string matches one of the valid policies.
|
|
|
|
func isPolicyValid(policy string) bool {
|
|
|
|
switch policy {
|
2020-03-24 19:29:34 +00:00
|
|
|
case PolicyDeny, PolicyRead, PolicyWrite, PolicyScale:
|
2017-08-04 00:41:33 +00:00
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-18 19:29:03 +00:00
|
|
|
func (p *PluginPolicy) isValid() bool {
|
|
|
|
switch p.Policy {
|
|
|
|
case PolicyDeny, PolicyRead, PolicyList:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-04 00:41:33 +00:00
|
|
|
// isNamespaceCapabilityValid ensures the given capability is valid for a namespace policy
|
|
|
|
func isNamespaceCapabilityValid(cap string) bool {
|
|
|
|
switch cap {
|
2017-08-08 04:09:13 +00:00
|
|
|
case NamespaceCapabilityDeny, NamespaceCapabilityListJobs, NamespaceCapabilityReadJob,
|
2017-09-28 14:27:51 +00:00
|
|
|
NamespaceCapabilitySubmitJob, NamespaceCapabilityDispatchJob, NamespaceCapabilityReadLogs,
|
2019-04-28 20:45:15 +00:00
|
|
|
NamespaceCapabilityReadFS, NamespaceCapabilityAllocLifecycle,
|
2019-10-28 15:59:28 +00:00
|
|
|
NamespaceCapabilityAllocExec, NamespaceCapabilityAllocNodeExec,
|
2020-03-22 14:21:51 +00:00
|
|
|
NamespaceCapabilityCSIReadVolume, NamespaceCapabilityCSIWriteVolume, NamespaceCapabilityCSIListVolume, NamespaceCapabilityCSIMountVolume, NamespaceCapabilityCSIRegisterPlugin,
|
|
|
|
NamespaceCapabilityListScalingPolicies, NamespaceCapabilityReadScalingPolicy, NamespaceCapabilityReadJobScaling, NamespaceCapabilityScaleJob:
|
2017-08-04 00:41:33 +00:00
|
|
|
return true
|
2017-09-26 22:26:33 +00:00
|
|
|
// Separate the enterprise-only capabilities
|
2017-09-19 14:47:10 +00:00
|
|
|
case NamespaceCapabilitySentinelOverride:
|
|
|
|
return true
|
2017-08-04 00:41:33 +00:00
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// expandNamespacePolicy provides the equivalent set of capabilities for
|
|
|
|
// a namespace policy
|
|
|
|
func expandNamespacePolicy(policy string) []string {
|
2020-03-17 21:32:39 +00:00
|
|
|
read := []string{
|
|
|
|
NamespaceCapabilityListJobs,
|
|
|
|
NamespaceCapabilityReadJob,
|
|
|
|
NamespaceCapabilityCSIListVolume,
|
|
|
|
NamespaceCapabilityCSIReadVolume,
|
2020-03-22 14:21:51 +00:00
|
|
|
NamespaceCapabilityReadJobScaling,
|
|
|
|
NamespaceCapabilityListScalingPolicies,
|
|
|
|
NamespaceCapabilityReadScalingPolicy,
|
2020-03-17 21:32:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
write := append(read, []string{
|
2020-03-22 14:21:51 +00:00
|
|
|
NamespaceCapabilityScaleJob,
|
2020-03-17 21:32:39 +00:00
|
|
|
NamespaceCapabilitySubmitJob,
|
|
|
|
NamespaceCapabilityDispatchJob,
|
|
|
|
NamespaceCapabilityReadLogs,
|
|
|
|
NamespaceCapabilityReadFS,
|
|
|
|
NamespaceCapabilityAllocExec,
|
|
|
|
NamespaceCapabilityAllocLifecycle,
|
|
|
|
NamespaceCapabilityCSIMountVolume,
|
|
|
|
NamespaceCapabilityCSIWriteVolume,
|
|
|
|
}...)
|
|
|
|
|
2017-08-04 00:41:33 +00:00
|
|
|
switch policy {
|
|
|
|
case PolicyDeny:
|
|
|
|
return []string{NamespaceCapabilityDeny}
|
|
|
|
case PolicyRead:
|
2020-03-17 21:32:39 +00:00
|
|
|
return read
|
2017-08-04 00:41:33 +00:00
|
|
|
case PolicyWrite:
|
2020-03-17 21:32:39 +00:00
|
|
|
return write
|
2020-03-24 19:29:34 +00:00
|
|
|
case PolicyScale:
|
2020-03-22 14:21:51 +00:00
|
|
|
return []string{
|
|
|
|
NamespaceCapabilityListScalingPolicies,
|
|
|
|
NamespaceCapabilityReadScalingPolicy,
|
|
|
|
NamespaceCapabilityReadJobScaling,
|
|
|
|
NamespaceCapabilityScaleJob,
|
|
|
|
}
|
2017-08-04 00:41:33 +00:00
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-08 11:14:24 +00:00
|
|
|
func isHostVolumeCapabilityValid(cap string) bool {
|
|
|
|
switch cap {
|
2019-08-21 18:13:16 +00:00
|
|
|
case HostVolumeCapabilityDeny, HostVolumeCapabilityMountReadOnly, HostVolumeCapabilityMountReadWrite:
|
2019-05-08 11:14:24 +00:00
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func expandHostVolumePolicy(policy string) []string {
|
|
|
|
switch policy {
|
|
|
|
case PolicyDeny:
|
|
|
|
return []string{HostVolumeCapabilityDeny}
|
|
|
|
case PolicyRead:
|
2019-08-21 18:13:16 +00:00
|
|
|
return []string{HostVolumeCapabilityMountReadOnly}
|
2019-05-08 11:14:24 +00:00
|
|
|
case PolicyWrite:
|
2019-08-21 18:13:16 +00:00
|
|
|
return []string{HostVolumeCapabilityMountReadOnly, HostVolumeCapabilityMountReadWrite}
|
2019-05-08 11:14:24 +00:00
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-04 00:41:33 +00:00
|
|
|
// Parse is used to parse the specified ACL rules into an
|
|
|
|
// intermediary set of policies, before being compiled into
|
|
|
|
// the ACL
|
|
|
|
func Parse(rules string) (*Policy, error) {
|
|
|
|
// Decode the rules
|
|
|
|
p := &Policy{Raw: rules}
|
|
|
|
if rules == "" {
|
|
|
|
// Hot path for empty rules
|
|
|
|
return p, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attempt to parse
|
2020-08-25 00:35:58 +00:00
|
|
|
if err := hclDecode(p, rules); err != nil {
|
2017-08-04 00:41:33 +00:00
|
|
|
return nil, fmt.Errorf("Failed to parse ACL Policy: %v", err)
|
|
|
|
}
|
|
|
|
|
2017-10-17 15:40:39 +00:00
|
|
|
// At least one valid policy must be specified, we don't want to store only
|
|
|
|
// raw data
|
2017-10-17 21:15:11 +00:00
|
|
|
if p.IsEmpty() {
|
2017-10-17 16:21:38 +00:00
|
|
|
return nil, fmt.Errorf("Invalid policy: %s", p.Raw)
|
2017-10-17 15:40:39 +00:00
|
|
|
}
|
|
|
|
|
2017-08-04 00:41:33 +00:00
|
|
|
// Validate the policy
|
|
|
|
for _, ns := range p.Namespaces {
|
2017-08-05 00:34:22 +00:00
|
|
|
if !validNamespace.MatchString(ns.Name) {
|
|
|
|
return nil, fmt.Errorf("Invalid namespace name: %#v", ns)
|
|
|
|
}
|
2017-08-04 00:41:33 +00:00
|
|
|
if ns.Policy != "" && !isPolicyValid(ns.Policy) {
|
|
|
|
return nil, fmt.Errorf("Invalid namespace policy: %#v", ns)
|
|
|
|
}
|
|
|
|
for _, cap := range ns.Capabilities {
|
|
|
|
if !isNamespaceCapabilityValid(cap) {
|
2017-08-08 04:09:13 +00:00
|
|
|
return nil, fmt.Errorf("Invalid namespace capability '%s': %#v", cap, ns)
|
2017-08-04 00:41:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expand the short hand policy to the capabilities and
|
|
|
|
// add to any existing capabilities
|
|
|
|
if ns.Policy != "" {
|
|
|
|
extraCap := expandNamespacePolicy(ns.Policy)
|
|
|
|
ns.Capabilities = append(ns.Capabilities, extraCap...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-08 11:14:24 +00:00
|
|
|
for _, hv := range p.HostVolumes {
|
|
|
|
if !validVolume.MatchString(hv.Name) {
|
|
|
|
return nil, fmt.Errorf("Invalid host volume name: %#v", hv)
|
|
|
|
}
|
|
|
|
if hv.Policy != "" && !isPolicyValid(hv.Policy) {
|
|
|
|
return nil, fmt.Errorf("Invalid host volume policy: %#v", hv)
|
|
|
|
}
|
|
|
|
for _, cap := range hv.Capabilities {
|
|
|
|
if !isHostVolumeCapabilityValid(cap) {
|
|
|
|
return nil, fmt.Errorf("Invalid host volume capability '%s': %#v", cap, hv)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expand the short hand policy to the capabilities and
|
|
|
|
// add to any existing capabilities
|
|
|
|
if hv.Policy != "" {
|
|
|
|
extraCap := expandHostVolumePolicy(hv.Policy)
|
|
|
|
hv.Capabilities = append(hv.Capabilities, extraCap...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-04 00:41:33 +00:00
|
|
|
if p.Agent != nil && !isPolicyValid(p.Agent.Policy) {
|
|
|
|
return nil, fmt.Errorf("Invalid agent policy: %#v", p.Agent)
|
|
|
|
}
|
|
|
|
|
|
|
|
if p.Node != nil && !isPolicyValid(p.Node.Policy) {
|
|
|
|
return nil, fmt.Errorf("Invalid node policy: %#v", p.Node)
|
|
|
|
}
|
|
|
|
|
|
|
|
if p.Operator != nil && !isPolicyValid(p.Operator.Policy) {
|
|
|
|
return nil, fmt.Errorf("Invalid operator policy: %#v", p.Operator)
|
|
|
|
}
|
2017-10-13 21:36:02 +00:00
|
|
|
|
|
|
|
if p.Quota != nil && !isPolicyValid(p.Quota.Policy) {
|
|
|
|
return nil, fmt.Errorf("Invalid quota policy: %#v", p.Quota)
|
|
|
|
}
|
2020-03-17 21:32:39 +00:00
|
|
|
|
2020-03-18 19:29:03 +00:00
|
|
|
if p.Plugin != nil && !p.Plugin.isValid() {
|
2020-03-17 21:32:39 +00:00
|
|
|
return nil, fmt.Errorf("Invalid plugin policy: %#v", p.Plugin)
|
|
|
|
}
|
2017-08-04 00:41:33 +00:00
|
|
|
return p, nil
|
|
|
|
}
|
2020-08-25 00:35:58 +00:00
|
|
|
|
|
|
|
// hclDecode wraps hcl.Decode function but handles any unexpected panics
|
|
|
|
func hclDecode(p *Policy, rules string) (err error) {
|
|
|
|
defer func() {
|
|
|
|
if rerr := recover(); rerr != nil {
|
|
|
|
err = fmt.Errorf("invalid acl policy: %v", rerr)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-08-25 15:28:55 +00:00
|
|
|
return hcl.Decode(p, rules)
|
2020-08-25 00:35:58 +00:00
|
|
|
}
|