github.com/manicqin/nomad@v0.9.5/acl/acl.go (about)

     1  package acl
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  
     8  	iradix "github.com/hashicorp/go-immutable-radix"
     9  	glob "github.com/ryanuber/go-glob"
    10  )
    11  
    12  // ManagementACL is a singleton used for management tokens
    13  var ManagementACL *ACL
    14  
    15  func init() {
    16  	var err error
    17  	ManagementACL, err = NewACL(true, nil)
    18  	if err != nil {
    19  		panic(fmt.Errorf("failed to setup management ACL: %v", err))
    20  	}
    21  }
    22  
    23  // capabilitySet is a type wrapper to help managing a set of capabilities
    24  type capabilitySet map[string]struct{}
    25  
    26  func (c capabilitySet) Check(k string) bool {
    27  	_, ok := c[k]
    28  	return ok
    29  }
    30  
    31  func (c capabilitySet) Set(k string) {
    32  	c[k] = struct{}{}
    33  }
    34  
    35  func (c capabilitySet) Clear() {
    36  	for cap := range c {
    37  		delete(c, cap)
    38  	}
    39  }
    40  
    41  // ACL object is used to convert a set of policies into a structure that
    42  // can be efficiently evaluated to determine if an action is allowed.
    43  type ACL struct {
    44  	// management tokens are allowed to do anything
    45  	management bool
    46  
    47  	// namespaces maps a namespace to a capabilitySet
    48  	namespaces *iradix.Tree
    49  
    50  	// wildcardNamespaces maps a glob pattern of a namespace to a capabilitySet
    51  	// We use an iradix for the purposes of ordered iteration.
    52  	wildcardNamespaces *iradix.Tree
    53  
    54  	// hostVolumes maps a named host volume to a capabilitySet
    55  	hostVolumes *iradix.Tree
    56  
    57  	// wildcardHostVolumes maps a glob pattern of host volume names to a capabilitySet
    58  	// We use an iradix for the purposes of ordered iteration.
    59  	wildcardHostVolumes *iradix.Tree
    60  
    61  	agent    string
    62  	nodeRPC  string
    63  	node     string
    64  	operator string
    65  	quota    string
    66  }
    67  
    68  // maxPrivilege returns the policy which grants the most privilege
    69  // This handles the case of Deny always taking maximum precedence.
    70  func maxPrivilege(a, b string) string {
    71  	switch {
    72  	case a == PolicyDeny || b == PolicyDeny:
    73  		return PolicyDeny
    74  	case a == PolicyWrite || b == PolicyWrite:
    75  		return PolicyWrite
    76  	case a == PolicyRead || b == PolicyRead:
    77  		return PolicyRead
    78  	default:
    79  		return ""
    80  	}
    81  }
    82  
    83  // NewACL compiles a set of policies into an ACL object
    84  func NewACL(management bool, policies []*Policy) (*ACL, error) {
    85  	// Hot-path management tokens
    86  	if management {
    87  		return &ACL{management: true}, nil
    88  	}
    89  
    90  	// Create the ACL object
    91  	acl := &ACL{}
    92  	nsTxn := iradix.New().Txn()
    93  	wnsTxn := iradix.New().Txn()
    94  	hvTxn := iradix.New().Txn()
    95  	whvTxn := iradix.New().Txn()
    96  
    97  	for _, policy := range policies {
    98  	NAMESPACES:
    99  		for _, ns := range policy.Namespaces {
   100  			// Should the namespace be matched using a glob?
   101  			globDefinition := strings.Contains(ns.Name, "*")
   102  
   103  			// Check for existing capabilities
   104  			var capabilities capabilitySet
   105  
   106  			if globDefinition {
   107  				raw, ok := wnsTxn.Get([]byte(ns.Name))
   108  				if ok {
   109  					capabilities = raw.(capabilitySet)
   110  				} else {
   111  					capabilities = make(capabilitySet)
   112  					wnsTxn.Insert([]byte(ns.Name), capabilities)
   113  				}
   114  			} else {
   115  				raw, ok := nsTxn.Get([]byte(ns.Name))
   116  				if ok {
   117  					capabilities = raw.(capabilitySet)
   118  				} else {
   119  					capabilities = make(capabilitySet)
   120  					nsTxn.Insert([]byte(ns.Name), capabilities)
   121  				}
   122  			}
   123  
   124  			// Deny always takes precedence
   125  			if capabilities.Check(NamespaceCapabilityDeny) {
   126  				continue NAMESPACES
   127  			}
   128  
   129  			// Add in all the capabilities
   130  			for _, cap := range ns.Capabilities {
   131  				if cap == NamespaceCapabilityDeny {
   132  					// Overwrite any existing capabilities
   133  					capabilities.Clear()
   134  					capabilities.Set(NamespaceCapabilityDeny)
   135  					continue NAMESPACES
   136  				}
   137  				capabilities.Set(cap)
   138  			}
   139  		}
   140  
   141  	HOSTVOLUMES:
   142  		for _, hv := range policy.HostVolumes {
   143  			// Should the volume be matched using a glob?
   144  			globDefinition := strings.Contains(hv.Name, "*")
   145  
   146  			// Check for existing capabilities
   147  			var capabilities capabilitySet
   148  
   149  			if globDefinition {
   150  				raw, ok := whvTxn.Get([]byte(hv.Name))
   151  				if ok {
   152  					capabilities = raw.(capabilitySet)
   153  				} else {
   154  					capabilities = make(capabilitySet)
   155  					whvTxn.Insert([]byte(hv.Name), capabilities)
   156  				}
   157  			} else {
   158  				raw, ok := hvTxn.Get([]byte(hv.Name))
   159  				if ok {
   160  					capabilities = raw.(capabilitySet)
   161  				} else {
   162  					capabilities = make(capabilitySet)
   163  					hvTxn.Insert([]byte(hv.Name), capabilities)
   164  				}
   165  			}
   166  
   167  			// Deny always takes precedence
   168  			if capabilities.Check(HostVolumeCapabilityDeny) {
   169  				continue
   170  			}
   171  
   172  			// Add in all the capabilities
   173  			for _, cap := range hv.Capabilities {
   174  				if cap == HostVolumeCapabilityDeny {
   175  					// Overwrite any existing capabilities
   176  					capabilities.Clear()
   177  					capabilities.Set(HostVolumeCapabilityDeny)
   178  					continue HOSTVOLUMES
   179  				}
   180  				capabilities.Set(cap)
   181  			}
   182  		}
   183  
   184  		// Take the maximum privilege for agent, node, and operator
   185  		if policy.Agent != nil {
   186  			acl.agent = maxPrivilege(acl.agent, policy.Agent.Policy)
   187  		}
   188  		if policy.Node != nil {
   189  			acl.node = maxPrivilege(acl.node, policy.Node.Policy)
   190  		}
   191  		if policy.NodeRPC != nil {
   192  			acl.nodeRPC = maxPrivilege(acl.nodeRPC, policy.NodeRPC.Policy)
   193  		}
   194  		if policy.Operator != nil {
   195  			acl.operator = maxPrivilege(acl.operator, policy.Operator.Policy)
   196  		}
   197  		if policy.Quota != nil {
   198  			acl.quota = maxPrivilege(acl.quota, policy.Quota.Policy)
   199  		}
   200  	}
   201  
   202  	// Finalize the namespaces
   203  	acl.namespaces = nsTxn.Commit()
   204  	acl.wildcardNamespaces = wnsTxn.Commit()
   205  	acl.hostVolumes = hvTxn.Commit()
   206  	acl.wildcardHostVolumes = whvTxn.Commit()
   207  
   208  	return acl, nil
   209  }
   210  
   211  // AllowNsOp is shorthand for AllowNamespaceOperation
   212  func (a *ACL) AllowNsOp(ns string, op string) bool {
   213  	return a.AllowNamespaceOperation(ns, op)
   214  }
   215  
   216  // AllowNamespaceOperation checks if a given operation is allowed for a namespace
   217  func (a *ACL) AllowNamespaceOperation(ns string, op string) bool {
   218  	// Hot path management tokens
   219  	if a.management {
   220  		return true
   221  	}
   222  
   223  	// Check for a matching capability set
   224  	capabilities, ok := a.matchingNamespaceCapabilitySet(ns)
   225  	if !ok {
   226  		return false
   227  	}
   228  
   229  	// Check if the capability has been granted
   230  	return capabilities.Check(op)
   231  }
   232  
   233  // AllowNamespace checks if any operations are allowed for a namespace
   234  func (a *ACL) AllowNamespace(ns string) bool {
   235  	// Hot path management tokens
   236  	if a.management {
   237  		return true
   238  	}
   239  
   240  	// Check for a matching capability set
   241  	capabilities, ok := a.matchingNamespaceCapabilitySet(ns)
   242  	if !ok {
   243  		return false
   244  	}
   245  
   246  	// Check if the capability has been granted
   247  	if len(capabilities) == 0 {
   248  		return false
   249  	}
   250  
   251  	return !capabilities.Check(PolicyDeny)
   252  }
   253  
   254  // AllowHostVolumeOperation checks if a given operation is allowed for a host volume
   255  func (a *ACL) AllowHostVolumeOperation(hv string, op string) bool {
   256  	// Hot path management tokens
   257  	if a.management {
   258  		return true
   259  	}
   260  
   261  	// Check for a matching capability set
   262  	capabilities, ok := a.matchingHostVolumeCapabilitySet(hv)
   263  	if !ok {
   264  		return false
   265  	}
   266  
   267  	// Check if the capability has been granted
   268  	return capabilities.Check(op)
   269  }
   270  
   271  // AllowHostVolume checks if any operations are allowed for a HostVolume
   272  func (a *ACL) AllowHostVolume(ns string) bool {
   273  	// Hot path management tokens
   274  	if a.management {
   275  		return true
   276  	}
   277  
   278  	// Check for a matching capability set
   279  	capabilities, ok := a.matchingHostVolumeCapabilitySet(ns)
   280  	if !ok {
   281  		return false
   282  	}
   283  
   284  	// Check if the capability has been granted
   285  	if len(capabilities) == 0 {
   286  		return false
   287  	}
   288  
   289  	return !capabilities.Check(PolicyDeny)
   290  }
   291  
   292  // matchingNamespaceCapabilitySet looks for a capabilitySet that matches the namespace,
   293  // if no concrete definitions are found, then we return the closest matching
   294  // glob.
   295  // The closest matching glob is the one that has the smallest character
   296  // difference between the namespace and the glob.
   297  func (a *ACL) matchingNamespaceCapabilitySet(ns string) (capabilitySet, bool) {
   298  	// Check for a concrete matching capability set
   299  	raw, ok := a.namespaces.Get([]byte(ns))
   300  	if ok {
   301  		return raw.(capabilitySet), true
   302  	}
   303  
   304  	// We didn't find a concrete match, so lets try and evaluate globs.
   305  	return a.findClosestMatchingGlob(a.wildcardNamespaces, ns)
   306  }
   307  
   308  // matchingHostVolumeCapabilitySet looks for a capabilitySet that matches the host volume name,
   309  // if no concrete definitions are found, then we return the closest matching
   310  // glob.
   311  // The closest matching glob is the one that has the smallest character
   312  // difference between the volume name and the glob.
   313  func (a *ACL) matchingHostVolumeCapabilitySet(name string) (capabilitySet, bool) {
   314  	// Check for a concrete matching capability set
   315  	raw, ok := a.hostVolumes.Get([]byte(name))
   316  	if ok {
   317  		return raw.(capabilitySet), true
   318  	}
   319  
   320  	// We didn't find a concrete match, so lets try and evaluate globs.
   321  	return a.findClosestMatchingGlob(a.wildcardHostVolumes, name)
   322  }
   323  
   324  type matchingGlob struct {
   325  	name          string
   326  	difference    int
   327  	capabilitySet capabilitySet
   328  }
   329  
   330  func (a *ACL) findClosestMatchingGlob(radix *iradix.Tree, ns string) (capabilitySet, bool) {
   331  	// First, find all globs that match.
   332  	matchingGlobs := findAllMatchingWildcards(radix, ns)
   333  
   334  	// If none match, let's return.
   335  	if len(matchingGlobs) == 0 {
   336  		return capabilitySet{}, false
   337  	}
   338  
   339  	// If a single matches, lets be efficient and return early.
   340  	if len(matchingGlobs) == 1 {
   341  		return matchingGlobs[0].capabilitySet, true
   342  	}
   343  
   344  	// Stable sort the matched globs, based on the character difference between
   345  	// the glob definition and the requested namespace. This allows us to be
   346  	// more consistent about results based on the policy definition.
   347  	sort.SliceStable(matchingGlobs, func(i, j int) bool {
   348  		return matchingGlobs[i].difference <= matchingGlobs[j].difference
   349  	})
   350  
   351  	return matchingGlobs[0].capabilitySet, true
   352  }
   353  
   354  func findAllMatchingWildcards(radix *iradix.Tree, name string) []matchingGlob {
   355  	var matches []matchingGlob
   356  
   357  	nsLen := len(name)
   358  
   359  	radix.Root().Walk(func(bk []byte, iv interface{}) bool {
   360  		k := string(bk)
   361  		v := iv.(capabilitySet)
   362  
   363  		isMatch := glob.Glob(k, name)
   364  		if isMatch {
   365  			pair := matchingGlob{
   366  				name:          k,
   367  				difference:    nsLen - len(k) + strings.Count(k, glob.GLOB),
   368  				capabilitySet: v,
   369  			}
   370  			matches = append(matches, pair)
   371  		}
   372  
   373  		// We always want to walk the entire tree, never terminate early.
   374  		return false
   375  	})
   376  
   377  	return matches
   378  }
   379  
   380  // AllowAgentRead checks if read operations are allowed for an agent
   381  func (a *ACL) AllowAgentRead() bool {
   382  	switch {
   383  	case a.management:
   384  		return true
   385  	case a.agent == PolicyWrite:
   386  		return true
   387  	case a.agent == PolicyRead:
   388  		return true
   389  	default:
   390  		return false
   391  	}
   392  }
   393  
   394  // AllowAgentWrite checks if write operations are allowed for an agent
   395  func (a *ACL) AllowAgentWrite() bool {
   396  	switch {
   397  	case a.management:
   398  		return true
   399  	case a.agent == PolicyWrite:
   400  		return true
   401  	default:
   402  		return false
   403  	}
   404  }
   405  
   406  // AllowNodeRead checks if read operations are allowed for a node
   407  func (a *ACL) AllowNodeRead() bool {
   408  	switch {
   409  	case a.management:
   410  		return true
   411  	case a.node == PolicyWrite:
   412  		return true
   413  	case a.node == PolicyRead:
   414  		return true
   415  	default:
   416  		return false
   417  	}
   418  }
   419  
   420  // AllowNodeWrite checks if write operations are allowed for a node
   421  func (a *ACL) AllowNodeWrite() bool {
   422  	switch {
   423  	case a.management:
   424  		return true
   425  	case a.node == PolicyWrite:
   426  		return true
   427  	default:
   428  		return false
   429  	}
   430  }
   431  
   432  // AllowNodeRead checks if read operations are allowed for a node
   433  func (a *ACL) AllowNodeRPCRead() bool {
   434  	switch {
   435  	case a.management:
   436  		return true
   437  	case a.nodeRPC == PolicyWrite:
   438  		return true
   439  	case a.nodeRPC == PolicyRead:
   440  		return true
   441  	default:
   442  		return false
   443  	}
   444  }
   445  
   446  // AllowNodeWrite checks if write operations are allowed for a node
   447  func (a *ACL) AllowNodeRPCWrite() bool {
   448  	switch {
   449  	case a.management:
   450  		return true
   451  	case a.nodeRPC == PolicyWrite:
   452  		return true
   453  	default:
   454  		return false
   455  	}
   456  }
   457  
   458  // AllowOperatorRead checks if read operations are allowed for a operator
   459  func (a *ACL) AllowOperatorRead() bool {
   460  	switch {
   461  	case a.management:
   462  		return true
   463  	case a.operator == PolicyWrite:
   464  		return true
   465  	case a.operator == PolicyRead:
   466  		return true
   467  	default:
   468  		return false
   469  	}
   470  }
   471  
   472  // AllowOperatorWrite checks if write operations are allowed for a operator
   473  func (a *ACL) AllowOperatorWrite() bool {
   474  	switch {
   475  	case a.management:
   476  		return true
   477  	case a.operator == PolicyWrite:
   478  		return true
   479  	default:
   480  		return false
   481  	}
   482  }
   483  
   484  // AllowQuotaRead checks if read operations are allowed for all quotas
   485  func (a *ACL) AllowQuotaRead() bool {
   486  	switch {
   487  	case a.management:
   488  		return true
   489  	case a.quota == PolicyWrite:
   490  		return true
   491  	case a.quota == PolicyRead:
   492  		return true
   493  	default:
   494  		return false
   495  	}
   496  }
   497  
   498  // AllowQuotaWrite checks if write operations are allowed for quotas
   499  func (a *ACL) AllowQuotaWrite() bool {
   500  	switch {
   501  	case a.management:
   502  		return true
   503  	case a.quota == PolicyWrite:
   504  		return true
   505  	default:
   506  		return false
   507  	}
   508  }
   509  
   510  // IsManagement checks if this represents a management token
   511  func (a *ACL) IsManagement() bool {
   512  	return a.management
   513  }
   514  
   515  // NamespaceValidator returns a func that wraps ACL.AllowNamespaceOperation in
   516  // a list of operations. Returns true (allowed) if acls are disabled or if
   517  // *any* capabilities match.
   518  func NamespaceValidator(ops ...string) func(*ACL, string) bool {
   519  	return func(acl *ACL, ns string) bool {
   520  		// Always allow if ACLs are disabled.
   521  		if acl == nil {
   522  			return true
   523  		}
   524  
   525  		for _, op := range ops {
   526  			if acl.AllowNamespaceOperation(ns, op) {
   527  				// An operation is allowed, return true
   528  				return true
   529  			}
   530  		}
   531  
   532  		// No operations are allowed by this ACL, return false
   533  		return false
   534  	}
   535  }