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