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