github.com/emate/nomad@v0.8.2-wo-binpacking/acl/acl.go (about)

     1  package acl
     2  
     3  import (
     4  	"fmt"
     5  
     6  	iradix "github.com/hashicorp/go-immutable-radix"
     7  )
     8  
     9  // ManagementACL is a singleton used for management tokens
    10  var ManagementACL *ACL
    11  
    12  func init() {
    13  	var err error
    14  	ManagementACL, err = NewACL(true, nil)
    15  	if err != nil {
    16  		panic(fmt.Errorf("failed to setup management ACL: %v", err))
    17  	}
    18  }
    19  
    20  // capabilitySet is a type wrapper to help managing a set of capabilities
    21  type capabilitySet map[string]struct{}
    22  
    23  func (c capabilitySet) Check(k string) bool {
    24  	_, ok := c[k]
    25  	return ok
    26  }
    27  
    28  func (c capabilitySet) Set(k string) {
    29  	c[k] = struct{}{}
    30  }
    31  
    32  func (c capabilitySet) Clear() {
    33  	for cap := range c {
    34  		delete(c, cap)
    35  	}
    36  }
    37  
    38  // ACL object is used to convert a set of policies into a structure that
    39  // can be efficiently evaluated to determine if an action is allowed.
    40  type ACL struct {
    41  	// management tokens are allowed to do anything
    42  	management bool
    43  
    44  	// namespaces maps a namespace to a capabilitySet
    45  	namespaces *iradix.Tree
    46  
    47  	agent    string
    48  	node     string
    49  	operator string
    50  	quota    string
    51  }
    52  
    53  // maxPrivilege returns the policy which grants the most privilege
    54  // This handles the case of Deny always taking maximum precedence.
    55  func maxPrivilege(a, b string) string {
    56  	switch {
    57  	case a == PolicyDeny || b == PolicyDeny:
    58  		return PolicyDeny
    59  	case a == PolicyWrite || b == PolicyWrite:
    60  		return PolicyWrite
    61  	case a == PolicyRead || b == PolicyRead:
    62  		return PolicyRead
    63  	default:
    64  		return ""
    65  	}
    66  }
    67  
    68  // NewACL compiles a set of policies into an ACL object
    69  func NewACL(management bool, policies []*Policy) (*ACL, error) {
    70  	// Hot-path management tokens
    71  	if management {
    72  		return &ACL{management: true}, nil
    73  	}
    74  
    75  	// Create the ACL object
    76  	acl := &ACL{}
    77  	nsTxn := iradix.New().Txn()
    78  
    79  	for _, policy := range policies {
    80  	NAMESPACES:
    81  		for _, ns := range policy.Namespaces {
    82  			// Check for existing capabilities
    83  			var capabilities capabilitySet
    84  			raw, ok := nsTxn.Get([]byte(ns.Name))
    85  			if ok {
    86  				capabilities = raw.(capabilitySet)
    87  			} else {
    88  				capabilities = make(capabilitySet)
    89  				nsTxn.Insert([]byte(ns.Name), capabilities)
    90  			}
    91  
    92  			// Deny always takes precedence
    93  			if capabilities.Check(NamespaceCapabilityDeny) {
    94  				continue NAMESPACES
    95  			}
    96  
    97  			// Add in all the capabilities
    98  			for _, cap := range ns.Capabilities {
    99  				if cap == NamespaceCapabilityDeny {
   100  					// Overwrite any existing capabilities
   101  					capabilities.Clear()
   102  					capabilities.Set(NamespaceCapabilityDeny)
   103  					continue NAMESPACES
   104  				}
   105  				capabilities.Set(cap)
   106  			}
   107  		}
   108  
   109  		// Take the maximum privilege for agent, node, and operator
   110  		if policy.Agent != nil {
   111  			acl.agent = maxPrivilege(acl.agent, policy.Agent.Policy)
   112  		}
   113  		if policy.Node != nil {
   114  			acl.node = maxPrivilege(acl.node, policy.Node.Policy)
   115  		}
   116  		if policy.Operator != nil {
   117  			acl.operator = maxPrivilege(acl.operator, policy.Operator.Policy)
   118  		}
   119  		if policy.Quota != nil {
   120  			acl.quota = maxPrivilege(acl.quota, policy.Quota.Policy)
   121  		}
   122  	}
   123  
   124  	// Finalize the namespaces
   125  	acl.namespaces = nsTxn.Commit()
   126  	return acl, nil
   127  }
   128  
   129  // AllowNsOp is shorthand for AllowNamespaceOperation
   130  func (a *ACL) AllowNsOp(ns string, op string) bool {
   131  	return a.AllowNamespaceOperation(ns, op)
   132  }
   133  
   134  // AllowNamespaceOperation checks if a given operation is allowed for a namespace
   135  func (a *ACL) AllowNamespaceOperation(ns string, op string) bool {
   136  	// Hot path management tokens
   137  	if a.management {
   138  		return true
   139  	}
   140  
   141  	// Check for a matching capability set
   142  	raw, ok := a.namespaces.Get([]byte(ns))
   143  	if !ok {
   144  		return false
   145  	}
   146  
   147  	// Check if the capability has been granted
   148  	capabilities := raw.(capabilitySet)
   149  	return capabilities.Check(op)
   150  }
   151  
   152  // AllowNamespace checks if any operations are allowed for a namespace
   153  func (a *ACL) AllowNamespace(ns string) bool {
   154  	// Hot path management tokens
   155  	if a.management {
   156  		return true
   157  	}
   158  
   159  	// Check for a matching capability set
   160  	raw, ok := a.namespaces.Get([]byte(ns))
   161  	if !ok {
   162  		return false
   163  	}
   164  
   165  	// Check if the capability has been granted
   166  	capabilities := raw.(capabilitySet)
   167  	if len(capabilities) == 0 {
   168  		return false
   169  	}
   170  
   171  	return !capabilities.Check(PolicyDeny)
   172  }
   173  
   174  // AllowAgentRead checks if read operations are allowed for an agent
   175  func (a *ACL) AllowAgentRead() bool {
   176  	switch {
   177  	case a.management:
   178  		return true
   179  	case a.agent == PolicyWrite:
   180  		return true
   181  	case a.agent == PolicyRead:
   182  		return true
   183  	default:
   184  		return false
   185  	}
   186  }
   187  
   188  // AllowAgentWrite checks if write operations are allowed for an agent
   189  func (a *ACL) AllowAgentWrite() bool {
   190  	switch {
   191  	case a.management:
   192  		return true
   193  	case a.agent == PolicyWrite:
   194  		return true
   195  	default:
   196  		return false
   197  	}
   198  }
   199  
   200  // AllowNodeRead checks if read operations are allowed for a node
   201  func (a *ACL) AllowNodeRead() bool {
   202  	switch {
   203  	case a.management:
   204  		return true
   205  	case a.node == PolicyWrite:
   206  		return true
   207  	case a.node == PolicyRead:
   208  		return true
   209  	default:
   210  		return false
   211  	}
   212  }
   213  
   214  // AllowNodeWrite checks if write operations are allowed for a node
   215  func (a *ACL) AllowNodeWrite() bool {
   216  	switch {
   217  	case a.management:
   218  		return true
   219  	case a.node == PolicyWrite:
   220  		return true
   221  	default:
   222  		return false
   223  	}
   224  }
   225  
   226  // AllowOperatorRead checks if read operations are allowed for a operator
   227  func (a *ACL) AllowOperatorRead() bool {
   228  	switch {
   229  	case a.management:
   230  		return true
   231  	case a.operator == PolicyWrite:
   232  		return true
   233  	case a.operator == PolicyRead:
   234  		return true
   235  	default:
   236  		return false
   237  	}
   238  }
   239  
   240  // AllowOperatorWrite checks if write operations are allowed for a operator
   241  func (a *ACL) AllowOperatorWrite() bool {
   242  	switch {
   243  	case a.management:
   244  		return true
   245  	case a.operator == PolicyWrite:
   246  		return true
   247  	default:
   248  		return false
   249  	}
   250  }
   251  
   252  // AllowQuotaRead checks if read operations are allowed for all quotas
   253  func (a *ACL) AllowQuotaRead() bool {
   254  	switch {
   255  	case a.management:
   256  		return true
   257  	case a.quota == PolicyWrite:
   258  		return true
   259  	case a.quota == PolicyRead:
   260  		return true
   261  	default:
   262  		return false
   263  	}
   264  }
   265  
   266  // AllowQuotaWrite checks if write operations are allowed for quotas
   267  func (a *ACL) AllowQuotaWrite() bool {
   268  	switch {
   269  	case a.management:
   270  		return true
   271  	case a.quota == PolicyWrite:
   272  		return true
   273  	default:
   274  		return false
   275  	}
   276  }
   277  
   278  // IsManagement checks if this represents a management token
   279  func (a *ACL) IsManagement() bool {
   280  	return a.management
   281  }