// aclApplyInternal is used to apply an ACL request after it has been vetted that // this is a valid operation. It is used when users are updating ACLs, in which // case we check their token to make sure they have management privileges. It is // also used for ACL replication. We want to run the replicated ACLs through the // same checks on the change itself. func aclApplyInternal(srv *Server, args *structs.ACLRequest, reply *string) error { // All ACLs must have an ID by this point. if args.ACL.ID == "" { return fmt.Errorf("Missing ACL ID") } switch args.Op { case structs.ACLSet: // Verify the ACL type switch args.ACL.Type { case structs.ACLTypeClient: case structs.ACLTypeManagement: default: return fmt.Errorf("Invalid ACL Type") } // Verify this is not a root ACL if acl.RootACL(args.ACL.ID) != nil { return fmt.Errorf("%s: Cannot modify root ACL", permissionDenied) } // Validate the rules compile _, err := acl.Parse(args.ACL.Rules) if err != nil { return fmt.Errorf("ACL rule compilation failed: %v", err) } case structs.ACLDelete: if args.ACL.ID == anonymousToken { return fmt.Errorf("%s: Cannot delete anonymous token", permissionDenied) } default: return fmt.Errorf("Invalid ACL Operation") } // Apply the update resp, err := srv.raftApply(structs.ACLRequestType, args) if err != nil { srv.logger.Printf("[ERR] consul.acl: Apply failed: %v", err) return err } if respErr, ok := resp.(error); ok { return respErr } // Check if the return type is a string if respString, ok := resp.(string); ok { *reply = respString } return nil }
// useACLPolicy handles an ACLPolicy response func (c *aclCache) useACLPolicy(id, authDC string, cached *aclCacheEntry, p *structs.ACLPolicy) (acl.ACL, error) { // Check if we can used the cached policy if cached != nil && cached.ETag == p.ETag { if p.TTL > 0 { // TODO (slackpad) - This seems like it's an unsafe // write. cached.Expires = time.Now().Add(p.TTL) } return cached.ACL, nil } // Check for a cached compiled policy var compiled acl.ACL raw, ok := c.policies.Get(p.ETag) if ok { compiled = raw.(acl.ACL) } else { // Resolve the parent policy parent := acl.RootACL(p.Parent) if parent == nil { var err error parent, err = c.lookupACL(p.Parent, authDC) if err != nil { return nil, err } } // Compile the ACL acl, err := acl.New(parent, p.Policy) if err != nil { return nil, err } // Cache the policy c.policies.Add(p.ETag, acl) compiled = acl } // Cache the ACL cached = &aclCacheEntry{ ACL: compiled, ETag: p.ETag, } if p.TTL > 0 { cached.Expires = time.Now().Add(p.TTL) } c.acls.Add(id, cached) return compiled, nil }
// useACLPolicy handles an ACLPolicy response func (s *Server) useACLPolicy(id, authDC string, cached *aclCacheEntry, p *structs.ACLPolicy) (acl.ACL, error) { // Check if we can used the cached policy if cached != nil && cached.ETag == p.ETag { if p.TTL > 0 { cached.Expires = time.Now().Add(p.TTL) } return cached.ACL, nil } // Check for a cached compiled policy var compiled acl.ACL raw, ok := s.aclPolicyCache.Get(p.ETag) if ok { compiled = raw.(acl.ACL) } else { // Resolve the parent policy parent := acl.RootACL(p.Parent) if parent == nil { var err error parent, err = s.lookupACL(p.Parent, authDC) if err != nil { return nil, err } } // Compile the ACL acl, err := acl.New(parent, p.Policy) if err != nil { return nil, err } // Cache the policy s.aclPolicyCache.Add(p.ETag, acl) compiled = acl } // Cache the ACL cached = &aclCacheEntry{ ACL: compiled, ETag: p.ETag, } if p.TTL > 0 { cached.Expires = time.Now().Add(p.TTL) } s.aclCache.Add(id, cached) return compiled, nil }
// resolveToken is used to resolve an ACL is any is appropriate func (s *Server) resolveToken(id string) (acl.ACL, error) { // Check if there is no ACL datacenter (ACL's disabled) authDC := s.config.ACLDatacenter if len(authDC) == 0 { return nil, nil } defer metrics.MeasureSince([]string{"consul", "acl", "resolveToken"}, time.Now()) // Handle the anonymous token if len(id) == 0 { id = anonymousToken } else if acl.RootACL(id) != nil { return nil, errors.New(rootDenied) } // Check if we are the ACL datacenter and the leader, use the // authoritative cache if s.config.Datacenter == authDC && s.IsLeader() { return s.aclAuthCache.GetACL(id) } // Use our non-authoritative cache return s.lookupACL(id, authDC) }
// Apply is used to apply a modifying request to the data store. This should // only be used for operations that modify the data func (a *ACL) Apply(args *structs.ACLRequest, reply *string) error { if done, err := a.srv.forward("ACL.Apply", args, args, reply); done { return err } defer metrics.MeasureSince([]string{"consul", "acl", "apply"}, time.Now()) // Verify we are allowed to serve this request if a.srv.config.ACLDatacenter != a.srv.config.Datacenter { return fmt.Errorf(aclDisabled) } // Verify token is permitted to modify ACLs if acl, err := a.srv.resolveToken(args.Token); err != nil { return err } else if acl == nil || !acl.ACLModify() { return permissionDeniedErr } switch args.Op { case structs.ACLSet: // Verify the ACL type switch args.ACL.Type { case structs.ACLTypeClient: case structs.ACLTypeManagement: default: return fmt.Errorf("Invalid ACL Type") } // Verify this is not a root ACL if acl.RootACL(args.ACL.ID) != nil { return fmt.Errorf("%s: Cannot modify root ACL", permissionDenied) } // Validate the rules compile _, err := acl.Parse(args.ACL.Rules) if err != nil { return fmt.Errorf("ACL rule compilation failed: %v", err) } case structs.ACLDelete: if args.ACL.ID == "" { return fmt.Errorf("Missing ACL ID") } else if args.ACL.ID == anonymousToken { return fmt.Errorf("%s: Cannot delete anonymous token", permissionDenied) } default: return fmt.Errorf("Invalid ACL Operation") } // Apply the update resp, err := a.srv.raftApply(structs.ACLRequestType, args) if err != nil { a.srv.logger.Printf("[ERR] consul.acl: Apply failed: %v", err) return err } if respErr, ok := resp.(error); ok { return respErr } // Clear the cache if applicable if args.ACL.ID != "" { a.srv.aclAuthCache.ClearACL(args.ACL.ID) } // Check if the return type is a string if respString, ok := resp.(string); ok { *reply = respString } return nil }
// Apply is used to apply a modifying request to the data store. This should // only be used for operations that modify the data func (a *ACL) Apply(args *structs.ACLRequest, reply *string) error { if done, err := a.srv.forward("ACL.Apply", args, args, reply); done { return err } defer metrics.MeasureSince([]string{"consul", "acl", "apply"}, time.Now()) // Verify we are allowed to serve this request if a.srv.config.ACLDatacenter != a.srv.config.Datacenter { return fmt.Errorf(aclDisabled) } // Verify token is permitted to modify ACLs if acl, err := a.srv.resolveToken(args.Token); err != nil { return err } else if acl == nil || !acl.ACLModify() { return permissionDeniedErr } switch args.Op { case structs.ACLSet: // Verify the ACL type switch args.ACL.Type { case structs.ACLTypeClient: case structs.ACLTypeManagement: default: return fmt.Errorf("Invalid ACL Type") } // Verify this is not a root ACL if acl.RootACL(args.ACL.ID) != nil { return fmt.Errorf("%s: Cannot modify root ACL", permissionDenied) } // Validate the rules compile _, err := acl.Parse(args.ACL.Rules) if err != nil { return fmt.Errorf("ACL rule compilation failed: %v", err) } // If no ID is provided, generate a new ID. This must // be done prior to appending to the raft log, because the ID is not // deterministic. Once the entry is in the log, the state update MUST // be deterministic or the followers will not converge. if args.ACL.ID == "" { state := a.srv.fsm.State() for { if args.ACL.ID, err = uuid.GenerateUUID(); err != nil { a.srv.logger.Printf("[ERR] consul.acl: UUID generation failed: %v", err) return err } _, acl, err := state.ACLGet(args.ACL.ID) if err != nil { a.srv.logger.Printf("[ERR] consul.acl: ACL lookup failed: %v", err) return err } if acl == nil { break } } } case structs.ACLDelete: if args.ACL.ID == "" { return fmt.Errorf("Missing ACL ID") } else if args.ACL.ID == anonymousToken { return fmt.Errorf("%s: Cannot delete anonymous token", permissionDenied) } default: return fmt.Errorf("Invalid ACL Operation") } // Apply the update resp, err := a.srv.raftApply(structs.ACLRequestType, args) if err != nil { a.srv.logger.Printf("[ERR] consul.acl: Apply failed: %v", err) return err } if respErr, ok := resp.(error); ok { return respErr } // Clear the cache if applicable if args.ACL.ID != "" { a.srv.aclAuthCache.ClearACL(args.ACL.ID) } // Check if the return type is a string if respString, ok := resp.(string); ok { *reply = respString } return nil }
// lookupACL attempts to locate the compiled policy associated with the given // token. The agent may be used to perform RPC calls to the servers to fetch // policies that aren't in the cache. func (m *aclManager) lookupACL(agent *Agent, id string) (acl.ACL, error) { // Handle some special cases for the ID. if len(id) == 0 { id = anonymousToken } else if acl.RootACL(id) != nil { return nil, errors.New(rootDenied) } else if m.master != nil && id == agent.config.ACLAgentMasterToken { return m.master, nil } // Try the cache first. var cached *aclCacheEntry if raw, ok := m.acls.Get(id); ok { cached = raw.(*aclCacheEntry) } if cached != nil && time.Now().Before(cached.Expires) { metrics.IncrCounter([]string{"consul", "acl", "cache_hit"}, 1) return cached.ACL, nil } else { metrics.IncrCounter([]string{"consul", "acl", "cache_miss"}, 1) } // At this point we might have a stale cached ACL, or none at all, so // try to contact the servers. args := structs.ACLPolicyRequest{ Datacenter: agent.config.Datacenter, ACL: id, } if cached != nil { args.ETag = cached.ETag } var reply structs.ACLPolicy err := agent.RPC(agent.getEndpoint("ACL")+".GetPolicy", &args, &reply) if err != nil { if strings.Contains(err.Error(), aclDisabled) { agent.logger.Printf("[DEBUG] agent: ACLs disabled on servers, will check again after %s", agent.config.ACLDisabledTTL) m.disabledLock.Lock() m.disabled = time.Now().Add(agent.config.ACLDisabledTTL) m.disabledLock.Unlock() return nil, nil } else if strings.Contains(err.Error(), aclNotFound) { return nil, errors.New(aclNotFound) } else { agent.logger.Printf("[DEBUG] agent: Failed to get policy for ACL from servers: %v", err) if m.down != nil { return m.down, nil } else if cached != nil { return cached.ACL, nil } else { return acl.DenyAll(), nil } } } // Use the old cached compiled ACL if we can, otherwise compile it and // resolve any parents. var compiled acl.ACL if cached != nil && cached.ETag == reply.ETag { compiled = cached.ACL } else { parent := acl.RootACL(reply.Parent) if parent == nil { parent, err = m.lookupACL(agent, reply.Parent) if err != nil { return nil, err } } acl, err := acl.New(parent, reply.Policy) if err != nil { return nil, err } compiled = acl } // Update the cache. cached = &aclCacheEntry{ ACL: compiled, ETag: reply.ETag, } if reply.TTL > 0 { cached.Expires = time.Now().Add(reply.TTL) } m.acls.Add(id, cached) return compiled, nil }