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 }