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