github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/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  	NamespaceCapabilityDeny             = "deny"
    25  	NamespaceCapabilityListJobs         = "list-jobs"
    26  	NamespaceCapabilityReadJob          = "read-job"
    27  	NamespaceCapabilitySubmitJob        = "submit-job"
    28  	NamespaceCapabilityDispatchJob      = "dispatch-job"
    29  	NamespaceCapabilityReadLogs         = "read-logs"
    30  	NamespaceCapabilityReadFS           = "read-fs"
    31  	NamespaceCapabilityAllocExec        = "alloc-exec"
    32  	NamespaceCapabilityAllocNodeExec    = "alloc-node-exec"
    33  	NamespaceCapabilityAllocLifecycle   = "alloc-lifecycle"
    34  	NamespaceCapabilitySentinelOverride = "sentinel-override"
    35  )
    36  
    37  var (
    38  	validNamespace = regexp.MustCompile("^[a-zA-Z0-9-*]{1,128}$")
    39  )
    40  
    41  // Policy represents a parsed HCL or JSON policy.
    42  type Policy struct {
    43  	Namespaces []*NamespacePolicy `hcl:"namespace,expand"`
    44  	Agent      *AgentPolicy       `hcl:"agent"`
    45  	Node       *NodePolicy        `hcl:"node"`
    46  	Operator   *OperatorPolicy    `hcl:"operator"`
    47  	Quota      *QuotaPolicy       `hcl:"quota"`
    48  	Raw        string             `hcl:"-"`
    49  }
    50  
    51  // IsEmpty checks to make sure that at least one policy has been set and is not
    52  // comprised of only a raw policy.
    53  func (p *Policy) IsEmpty() bool {
    54  	return len(p.Namespaces) == 0 &&
    55  		p.Agent == nil &&
    56  		p.Node == nil &&
    57  		p.Operator == nil &&
    58  		p.Quota == nil
    59  }
    60  
    61  // NamespacePolicy is the policy for a specific namespace
    62  type NamespacePolicy struct {
    63  	Name         string `hcl:",key"`
    64  	Policy       string
    65  	Capabilities []string
    66  }
    67  
    68  type AgentPolicy struct {
    69  	Policy string
    70  }
    71  
    72  type NodePolicy struct {
    73  	Policy string
    74  }
    75  
    76  type OperatorPolicy struct {
    77  	Policy string
    78  }
    79  
    80  type QuotaPolicy struct {
    81  	Policy string
    82  }
    83  
    84  // isPolicyValid makes sure the given string matches one of the valid policies.
    85  func isPolicyValid(policy string) bool {
    86  	switch policy {
    87  	case PolicyDeny, PolicyRead, PolicyWrite:
    88  		return true
    89  	default:
    90  		return false
    91  	}
    92  }
    93  
    94  // isNamespaceCapabilityValid ensures the given capability is valid for a namespace policy
    95  func isNamespaceCapabilityValid(cap string) bool {
    96  	switch cap {
    97  	case NamespaceCapabilityDeny, NamespaceCapabilityListJobs, NamespaceCapabilityReadJob,
    98  		NamespaceCapabilitySubmitJob, NamespaceCapabilityDispatchJob, NamespaceCapabilityReadLogs,
    99  		NamespaceCapabilityReadFS, NamespaceCapabilityAllocLifecycle,
   100  		NamespaceCapabilityAllocExec, NamespaceCapabilityAllocNodeExec:
   101  		return true
   102  	// Separate the enterprise-only capabilities
   103  	case NamespaceCapabilitySentinelOverride:
   104  		return true
   105  	default:
   106  		return false
   107  	}
   108  }
   109  
   110  // expandNamespacePolicy provides the equivalent set of capabilities for
   111  // a namespace policy
   112  func expandNamespacePolicy(policy string) []string {
   113  	switch policy {
   114  	case PolicyDeny:
   115  		return []string{NamespaceCapabilityDeny}
   116  	case PolicyRead:
   117  		return []string{
   118  			NamespaceCapabilityListJobs,
   119  			NamespaceCapabilityReadJob,
   120  		}
   121  	case PolicyWrite:
   122  		return []string{
   123  			NamespaceCapabilityListJobs,
   124  			NamespaceCapabilityReadJob,
   125  			NamespaceCapabilitySubmitJob,
   126  			NamespaceCapabilityDispatchJob,
   127  			NamespaceCapabilityReadLogs,
   128  			NamespaceCapabilityReadFS,
   129  			NamespaceCapabilityAllocExec,
   130  			NamespaceCapabilityAllocLifecycle,
   131  		}
   132  	default:
   133  		return nil
   134  	}
   135  }
   136  
   137  // Parse is used to parse the specified ACL rules into an
   138  // intermediary set of policies, before being compiled into
   139  // the ACL
   140  func Parse(rules string) (*Policy, error) {
   141  	// Decode the rules
   142  	p := &Policy{Raw: rules}
   143  	if rules == "" {
   144  		// Hot path for empty rules
   145  		return p, nil
   146  	}
   147  
   148  	// Attempt to parse
   149  	if err := hcl.Decode(p, rules); err != nil {
   150  		return nil, fmt.Errorf("Failed to parse ACL Policy: %v", err)
   151  	}
   152  
   153  	// At least one valid policy must be specified, we don't want to store only
   154  	// raw data
   155  	if p.IsEmpty() {
   156  		return nil, fmt.Errorf("Invalid policy: %s", p.Raw)
   157  	}
   158  
   159  	// Validate the policy
   160  	for _, ns := range p.Namespaces {
   161  		if !validNamespace.MatchString(ns.Name) {
   162  			return nil, fmt.Errorf("Invalid namespace name: %#v", ns)
   163  		}
   164  		if ns.Policy != "" && !isPolicyValid(ns.Policy) {
   165  			return nil, fmt.Errorf("Invalid namespace policy: %#v", ns)
   166  		}
   167  		for _, cap := range ns.Capabilities {
   168  			if !isNamespaceCapabilityValid(cap) {
   169  				return nil, fmt.Errorf("Invalid namespace capability '%s': %#v", cap, ns)
   170  			}
   171  		}
   172  
   173  		// Expand the short hand policy to the capabilities and
   174  		// add to any existing capabilities
   175  		if ns.Policy != "" {
   176  			extraCap := expandNamespacePolicy(ns.Policy)
   177  			ns.Capabilities = append(ns.Capabilities, extraCap...)
   178  		}
   179  	}
   180  
   181  	if p.Agent != nil && !isPolicyValid(p.Agent.Policy) {
   182  		return nil, fmt.Errorf("Invalid agent policy: %#v", p.Agent)
   183  	}
   184  
   185  	if p.Node != nil && !isPolicyValid(p.Node.Policy) {
   186  		return nil, fmt.Errorf("Invalid node policy: %#v", p.Node)
   187  	}
   188  
   189  	if p.Operator != nil && !isPolicyValid(p.Operator.Policy) {
   190  		return nil, fmt.Errorf("Invalid operator policy: %#v", p.Operator)
   191  	}
   192  
   193  	if p.Quota != nil && !isPolicyValid(p.Quota.Policy) {
   194  		return nil, fmt.Errorf("Invalid quota policy: %#v", p.Quota)
   195  	}
   196  	return p, nil
   197  }