github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/acl/policy.go (about)

     1  package acl
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  
     7  	"github.com/hashicorp/hcl"
     8  )
     9  
    10  const (
    11  	// The following levels are the only valid values for the `policy = "read"` stanza.
    12  	// When policies are merged together, the most privilege is granted, except for deny
    13  	// which always takes precedence and supersedes.
    14  	PolicyDeny  = "deny"
    15  	PolicyRead  = "read"
    16  	PolicyList  = "list"
    17  	PolicyWrite = "write"
    18  	PolicyScale = "scale"
    19  )
    20  
    21  const (
    22  	// The following are the fine-grained capabilities that can be granted within a namespace.
    23  	// The Policy stanza is a short hand for granting several of these. When capabilities are
    24  	// combined we take the union of all capabilities. If the deny capability is present, it
    25  	// takes precedence and overwrites all other capabilities.
    26  
    27  	NamespaceCapabilityDeny                 = "deny"
    28  	NamespaceCapabilityListJobs             = "list-jobs"
    29  	NamespaceCapabilityReadJob              = "read-job"
    30  	NamespaceCapabilitySubmitJob            = "submit-job"
    31  	NamespaceCapabilityDispatchJob          = "dispatch-job"
    32  	NamespaceCapabilityReadLogs             = "read-logs"
    33  	NamespaceCapabilityReadFS               = "read-fs"
    34  	NamespaceCapabilityAllocExec            = "alloc-exec"
    35  	NamespaceCapabilityAllocNodeExec        = "alloc-node-exec"
    36  	NamespaceCapabilityAllocLifecycle       = "alloc-lifecycle"
    37  	NamespaceCapabilitySentinelOverride     = "sentinel-override"
    38  	NamespaceCapabilityCSIRegisterPlugin    = "csi-register-plugin"
    39  	NamespaceCapabilityCSIWriteVolume       = "csi-write-volume"
    40  	NamespaceCapabilityCSIReadVolume        = "csi-read-volume"
    41  	NamespaceCapabilityCSIListVolume        = "csi-list-volume"
    42  	NamespaceCapabilityCSIMountVolume       = "csi-mount-volume"
    43  	NamespaceCapabilityListScalingPolicies  = "list-scaling-policies"
    44  	NamespaceCapabilityReadScalingPolicy    = "read-scaling-policy"
    45  	NamespaceCapabilityReadJobScaling       = "read-job-scaling"
    46  	NamespaceCapabilityScaleJob             = "scale-job"
    47  	NamespaceCapabilitySubmitRecommendation = "submit-recommendation"
    48  )
    49  
    50  var (
    51  	validNamespace = regexp.MustCompile("^[a-zA-Z0-9-*]{1,128}$")
    52  )
    53  
    54  const (
    55  	// The following are the fine-grained capabilities that can be granted for a volume set.
    56  	// The Policy stanza is a short hand for granting several of these. When capabilities are
    57  	// combined we take the union of all capabilities. If the deny capability is present, it
    58  	// takes precedence and overwrites all other capabilities.
    59  
    60  	HostVolumeCapabilityDeny           = "deny"
    61  	HostVolumeCapabilityMountReadOnly  = "mount-readonly"
    62  	HostVolumeCapabilityMountReadWrite = "mount-readwrite"
    63  )
    64  
    65  var (
    66  	validVolume = regexp.MustCompile("^[a-zA-Z0-9-*]{1,128}$")
    67  )
    68  
    69  // Policy represents a parsed HCL or JSON policy.
    70  type Policy struct {
    71  	Namespaces  []*NamespacePolicy  `hcl:"namespace,expand"`
    72  	HostVolumes []*HostVolumePolicy `hcl:"host_volume,expand"`
    73  	Agent       *AgentPolicy        `hcl:"agent"`
    74  	Node        *NodePolicy         `hcl:"node"`
    75  	Operator    *OperatorPolicy     `hcl:"operator"`
    76  	Quota       *QuotaPolicy        `hcl:"quota"`
    77  	Plugin      *PluginPolicy       `hcl:"plugin"`
    78  	Raw         string              `hcl:"-"`
    79  }
    80  
    81  // IsEmpty checks to make sure that at least one policy has been set and is not
    82  // comprised of only a raw policy.
    83  func (p *Policy) IsEmpty() bool {
    84  	return len(p.Namespaces) == 0 &&
    85  		len(p.HostVolumes) == 0 &&
    86  		p.Agent == nil &&
    87  		p.Node == nil &&
    88  		p.Operator == nil &&
    89  		p.Quota == nil &&
    90  		p.Plugin == nil
    91  }
    92  
    93  // NamespacePolicy is the policy for a specific namespace
    94  type NamespacePolicy struct {
    95  	Name         string `hcl:",key"`
    96  	Policy       string
    97  	Capabilities []string
    98  }
    99  
   100  // HostVolumePolicy is the policy for a specific named host volume
   101  type HostVolumePolicy struct {
   102  	Name         string `hcl:",key"`
   103  	Policy       string
   104  	Capabilities []string
   105  }
   106  
   107  type AgentPolicy struct {
   108  	Policy string
   109  }
   110  
   111  type NodePolicy struct {
   112  	Policy string
   113  }
   114  
   115  type OperatorPolicy struct {
   116  	Policy string
   117  }
   118  
   119  type QuotaPolicy struct {
   120  	Policy string
   121  }
   122  
   123  type PluginPolicy struct {
   124  	Policy string
   125  }
   126  
   127  // isPolicyValid makes sure the given string matches one of the valid policies.
   128  func isPolicyValid(policy string) bool {
   129  	switch policy {
   130  	case PolicyDeny, PolicyRead, PolicyWrite, PolicyScale:
   131  		return true
   132  	default:
   133  		return false
   134  	}
   135  }
   136  
   137  func (p *PluginPolicy) isValid() bool {
   138  	switch p.Policy {
   139  	case PolicyDeny, PolicyRead, PolicyList:
   140  		return true
   141  	default:
   142  		return false
   143  	}
   144  }
   145  
   146  // isNamespaceCapabilityValid ensures the given capability is valid for a namespace policy
   147  func isNamespaceCapabilityValid(cap string) bool {
   148  	switch cap {
   149  	case NamespaceCapabilityDeny, NamespaceCapabilityListJobs, NamespaceCapabilityReadJob,
   150  		NamespaceCapabilitySubmitJob, NamespaceCapabilityDispatchJob, NamespaceCapabilityReadLogs,
   151  		NamespaceCapabilityReadFS, NamespaceCapabilityAllocLifecycle,
   152  		NamespaceCapabilityAllocExec, NamespaceCapabilityAllocNodeExec,
   153  		NamespaceCapabilityCSIReadVolume, NamespaceCapabilityCSIWriteVolume, NamespaceCapabilityCSIListVolume, NamespaceCapabilityCSIMountVolume, NamespaceCapabilityCSIRegisterPlugin,
   154  		NamespaceCapabilityListScalingPolicies, NamespaceCapabilityReadScalingPolicy, NamespaceCapabilityReadJobScaling, NamespaceCapabilityScaleJob:
   155  		return true
   156  	// Separate the enterprise-only capabilities
   157  	case NamespaceCapabilitySentinelOverride, NamespaceCapabilitySubmitRecommendation:
   158  		return true
   159  	default:
   160  		return false
   161  	}
   162  }
   163  
   164  // expandNamespacePolicy provides the equivalent set of capabilities for
   165  // a namespace policy
   166  func expandNamespacePolicy(policy string) []string {
   167  	read := []string{
   168  		NamespaceCapabilityListJobs,
   169  		NamespaceCapabilityReadJob,
   170  		NamespaceCapabilityCSIListVolume,
   171  		NamespaceCapabilityCSIReadVolume,
   172  		NamespaceCapabilityReadJobScaling,
   173  		NamespaceCapabilityListScalingPolicies,
   174  		NamespaceCapabilityReadScalingPolicy,
   175  	}
   176  
   177  	write := append(read, []string{
   178  		NamespaceCapabilityScaleJob,
   179  		NamespaceCapabilitySubmitJob,
   180  		NamespaceCapabilityDispatchJob,
   181  		NamespaceCapabilityReadLogs,
   182  		NamespaceCapabilityReadFS,
   183  		NamespaceCapabilityAllocExec,
   184  		NamespaceCapabilityAllocLifecycle,
   185  		NamespaceCapabilityCSIMountVolume,
   186  		NamespaceCapabilityCSIWriteVolume,
   187  		NamespaceCapabilitySubmitRecommendation,
   188  	}...)
   189  
   190  	switch policy {
   191  	case PolicyDeny:
   192  		return []string{NamespaceCapabilityDeny}
   193  	case PolicyRead:
   194  		return read
   195  	case PolicyWrite:
   196  		return write
   197  	case PolicyScale:
   198  		return []string{
   199  			NamespaceCapabilityListScalingPolicies,
   200  			NamespaceCapabilityReadScalingPolicy,
   201  			NamespaceCapabilityReadJobScaling,
   202  			NamespaceCapabilityScaleJob,
   203  		}
   204  	default:
   205  		return nil
   206  	}
   207  }
   208  
   209  func isHostVolumeCapabilityValid(cap string) bool {
   210  	switch cap {
   211  	case HostVolumeCapabilityDeny, HostVolumeCapabilityMountReadOnly, HostVolumeCapabilityMountReadWrite:
   212  		return true
   213  	default:
   214  		return false
   215  	}
   216  }
   217  
   218  func expandHostVolumePolicy(policy string) []string {
   219  	switch policy {
   220  	case PolicyDeny:
   221  		return []string{HostVolumeCapabilityDeny}
   222  	case PolicyRead:
   223  		return []string{HostVolumeCapabilityMountReadOnly}
   224  	case PolicyWrite:
   225  		return []string{HostVolumeCapabilityMountReadOnly, HostVolumeCapabilityMountReadWrite}
   226  	default:
   227  		return nil
   228  	}
   229  }
   230  
   231  // Parse is used to parse the specified ACL rules into an
   232  // intermediary set of policies, before being compiled into
   233  // the ACL
   234  func Parse(rules string) (*Policy, error) {
   235  	// Decode the rules
   236  	p := &Policy{Raw: rules}
   237  	if rules == "" {
   238  		// Hot path for empty rules
   239  		return p, nil
   240  	}
   241  
   242  	// Attempt to parse
   243  	if err := hclDecode(p, rules); err != nil {
   244  		return nil, fmt.Errorf("Failed to parse ACL Policy: %v", err)
   245  	}
   246  
   247  	// At least one valid policy must be specified, we don't want to store only
   248  	// raw data
   249  	if p.IsEmpty() {
   250  		return nil, fmt.Errorf("Invalid policy: %s", p.Raw)
   251  	}
   252  
   253  	// Validate the policy
   254  	for _, ns := range p.Namespaces {
   255  		if !validNamespace.MatchString(ns.Name) {
   256  			return nil, fmt.Errorf("Invalid namespace name: %#v", ns)
   257  		}
   258  		if ns.Policy != "" && !isPolicyValid(ns.Policy) {
   259  			return nil, fmt.Errorf("Invalid namespace policy: %#v", ns)
   260  		}
   261  		for _, cap := range ns.Capabilities {
   262  			if !isNamespaceCapabilityValid(cap) {
   263  				return nil, fmt.Errorf("Invalid namespace capability '%s': %#v", cap, ns)
   264  			}
   265  		}
   266  
   267  		// Expand the short hand policy to the capabilities and
   268  		// add to any existing capabilities
   269  		if ns.Policy != "" {
   270  			extraCap := expandNamespacePolicy(ns.Policy)
   271  			ns.Capabilities = append(ns.Capabilities, extraCap...)
   272  		}
   273  	}
   274  
   275  	for _, hv := range p.HostVolumes {
   276  		if !validVolume.MatchString(hv.Name) {
   277  			return nil, fmt.Errorf("Invalid host volume name: %#v", hv)
   278  		}
   279  		if hv.Policy != "" && !isPolicyValid(hv.Policy) {
   280  			return nil, fmt.Errorf("Invalid host volume policy: %#v", hv)
   281  		}
   282  		for _, cap := range hv.Capabilities {
   283  			if !isHostVolumeCapabilityValid(cap) {
   284  				return nil, fmt.Errorf("Invalid host volume capability '%s': %#v", cap, hv)
   285  			}
   286  		}
   287  
   288  		// Expand the short hand policy to the capabilities and
   289  		// add to any existing capabilities
   290  		if hv.Policy != "" {
   291  			extraCap := expandHostVolumePolicy(hv.Policy)
   292  			hv.Capabilities = append(hv.Capabilities, extraCap...)
   293  		}
   294  	}
   295  
   296  	if p.Agent != nil && !isPolicyValid(p.Agent.Policy) {
   297  		return nil, fmt.Errorf("Invalid agent policy: %#v", p.Agent)
   298  	}
   299  
   300  	if p.Node != nil && !isPolicyValid(p.Node.Policy) {
   301  		return nil, fmt.Errorf("Invalid node policy: %#v", p.Node)
   302  	}
   303  
   304  	if p.Operator != nil && !isPolicyValid(p.Operator.Policy) {
   305  		return nil, fmt.Errorf("Invalid operator policy: %#v", p.Operator)
   306  	}
   307  
   308  	if p.Quota != nil && !isPolicyValid(p.Quota.Policy) {
   309  		return nil, fmt.Errorf("Invalid quota policy: %#v", p.Quota)
   310  	}
   311  
   312  	if p.Plugin != nil && !p.Plugin.isValid() {
   313  		return nil, fmt.Errorf("Invalid plugin policy: %#v", p.Plugin)
   314  	}
   315  	return p, nil
   316  }
   317  
   318  // hclDecode wraps hcl.Decode function but handles any unexpected panics
   319  func hclDecode(p *Policy, rules string) (err error) {
   320  	defer func() {
   321  		if rerr := recover(); rerr != nil {
   322  			err = fmt.Errorf("invalid acl policy: %v", rerr)
   323  		}
   324  	}()
   325  
   326  	return hcl.Decode(p, rules)
   327  }