github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/client/acl.go (about)

     1  package client
     2  
     3  import (
     4  	"time"
     5  
     6  	metrics "github.com/armon/go-metrics"
     7  	lru "github.com/hashicorp/golang-lru"
     8  	"github.com/hashicorp/nomad/acl"
     9  	"github.com/hashicorp/nomad/nomad/structs"
    10  )
    11  
    12  const (
    13  	// policyCacheSize is the number of ACL policies to keep cached. Policies have a fetching cost
    14  	// so we keep the hot policies cached to reduce the ACL token resolution time.
    15  	policyCacheSize = 64
    16  
    17  	// aclCacheSize is the number of ACL objects to keep cached. ACLs have a parsing and
    18  	// construction cost, so we keep the hot objects cached to reduce the ACL token resolution time.
    19  	aclCacheSize = 64
    20  
    21  	// tokenCacheSize is the number of ACL tokens to keep cached. Tokens have a fetching cost,
    22  	// so we keep the hot tokens cached to reduce the lookups.
    23  	tokenCacheSize = 64
    24  )
    25  
    26  // clientACLResolver holds the state required for client resolution
    27  // of ACLs
    28  type clientACLResolver struct {
    29  	// aclCache is used to maintain the parsed ACL objects
    30  	aclCache *lru.TwoQueueCache
    31  
    32  	// policyCache is used to maintain the fetched policy objects
    33  	policyCache *lru.TwoQueueCache
    34  
    35  	// tokenCache is used to maintain the fetched token objects
    36  	tokenCache *lru.TwoQueueCache
    37  }
    38  
    39  // init is used to setup the client resolver state
    40  func (c *clientACLResolver) init() error {
    41  	// Create the ACL object cache
    42  	var err error
    43  	c.aclCache, err = lru.New2Q(aclCacheSize)
    44  	if err != nil {
    45  		return err
    46  	}
    47  	c.policyCache, err = lru.New2Q(policyCacheSize)
    48  	if err != nil {
    49  		return err
    50  	}
    51  	c.tokenCache, err = lru.New2Q(tokenCacheSize)
    52  	if err != nil {
    53  		return err
    54  	}
    55  	return nil
    56  }
    57  
    58  // cachedACLValue is used to manage ACL Token or Policy TTLs
    59  type cachedACLValue struct {
    60  	Token     *structs.ACLToken
    61  	Policy    *structs.ACLPolicy
    62  	CacheTime time.Time
    63  }
    64  
    65  // Age is the time since the token was cached
    66  func (c *cachedACLValue) Age() time.Duration {
    67  	return time.Since(c.CacheTime)
    68  }
    69  
    70  // ResolveToken is used to translate an ACL Token Secret ID into
    71  // an ACL object, nil if ACLs are disabled, or an error.
    72  func (c *Client) ResolveToken(secretID string) (*acl.ACL, error) {
    73  	a, _, err := c.resolveTokenAndACL(secretID)
    74  	return a, err
    75  }
    76  
    77  func (c *Client) ResolveSecretToken(secretID string) (*structs.ACLToken, error) {
    78  	_, t, err := c.resolveTokenAndACL(secretID)
    79  	return t, err
    80  }
    81  
    82  func (c *Client) resolveTokenAndACL(secretID string) (*acl.ACL, *structs.ACLToken, error) {
    83  	// Fast-path if ACLs are disabled
    84  	if !c.config.ACLEnabled {
    85  		return nil, nil, nil
    86  	}
    87  	defer metrics.MeasureSince([]string{"client", "acl", "resolve_token"}, time.Now())
    88  
    89  	// Resolve the token value
    90  	token, err := c.resolveTokenValue(secretID)
    91  	if err != nil {
    92  		return nil, nil, err
    93  	}
    94  	if token == nil {
    95  		return nil, nil, structs.ErrTokenNotFound
    96  	}
    97  
    98  	// Check if this is a management token
    99  	if token.Type == structs.ACLManagementToken {
   100  		return acl.ManagementACL, token, nil
   101  	}
   102  
   103  	// Resolve the policies
   104  	policies, err := c.resolvePolicies(token.SecretID, token.Policies)
   105  	if err != nil {
   106  		return nil, nil, err
   107  	}
   108  
   109  	// Resolve the ACL object
   110  	aclObj, err := structs.CompileACLObject(c.aclCache, policies)
   111  	if err != nil {
   112  		return nil, nil, err
   113  	}
   114  	return aclObj, token, nil
   115  }
   116  
   117  // resolveTokenValue is used to translate a secret ID into an ACL token with caching
   118  // We use a local cache up to the TTL limit, and then resolve via a server. If we cannot
   119  // reach a server, but have a cached value we extend the TTL to gracefully handle outages.
   120  func (c *Client) resolveTokenValue(secretID string) (*structs.ACLToken, error) {
   121  	// Hot-path the anonymous token
   122  	if secretID == "" {
   123  		return structs.AnonymousACLToken, nil
   124  	}
   125  
   126  	// Lookup the token in the cache
   127  	raw, ok := c.tokenCache.Get(secretID)
   128  	if ok {
   129  		cached := raw.(*cachedACLValue)
   130  		if cached.Age() <= c.config.ACLTokenTTL {
   131  			return cached.Token, nil
   132  		}
   133  	}
   134  
   135  	// Lookup the token
   136  	req := structs.ResolveACLTokenRequest{
   137  		SecretID: secretID,
   138  		QueryOptions: structs.QueryOptions{
   139  			Region:     c.Region(),
   140  			AllowStale: true,
   141  		},
   142  	}
   143  	var resp structs.ResolveACLTokenResponse
   144  	if err := c.RPC("ACL.ResolveToken", &req, &resp); err != nil {
   145  		// If we encounter an error but have a cached value, mask the error and extend the cache
   146  		if ok {
   147  			c.logger.Warn("failed to resolve token, using expired cached value", "error", err)
   148  			cached := raw.(*cachedACLValue)
   149  			return cached.Token, nil
   150  		}
   151  		return nil, err
   152  	}
   153  
   154  	// Cache the response (positive or negative)
   155  	c.tokenCache.Add(secretID, &cachedACLValue{
   156  		Token:     resp.Token,
   157  		CacheTime: time.Now(),
   158  	})
   159  	return resp.Token, nil
   160  }
   161  
   162  // resolvePolicies is used to translate a set of named ACL policies into the objects.
   163  // We cache the policies locally, and fault them from a server as necessary. Policies
   164  // are cached for a TTL, and then refreshed. If a server cannot be reached, the cache TTL
   165  // will be ignored to gracefully handle outages.
   166  func (c *Client) resolvePolicies(secretID string, policies []string) ([]*structs.ACLPolicy, error) {
   167  	var out []*structs.ACLPolicy
   168  	var expired []*structs.ACLPolicy
   169  	var missing []string
   170  
   171  	// Scan the cache for each policy
   172  	for _, policyName := range policies {
   173  		// Lookup the policy in the cache
   174  		raw, ok := c.policyCache.Get(policyName)
   175  		if !ok {
   176  			missing = append(missing, policyName)
   177  			continue
   178  		}
   179  
   180  		// Check if the cached value is valid or expired
   181  		cached := raw.(*cachedACLValue)
   182  		if cached.Age() <= c.config.ACLPolicyTTL {
   183  			out = append(out, cached.Policy)
   184  		} else {
   185  			expired = append(expired, cached.Policy)
   186  		}
   187  	}
   188  
   189  	// Hot-path if we have no missing or expired policies
   190  	if len(missing)+len(expired) == 0 {
   191  		return out, nil
   192  	}
   193  
   194  	// Lookup the missing and expired policies
   195  	fetch := missing
   196  	for _, p := range expired {
   197  		fetch = append(fetch, p.Name)
   198  	}
   199  	req := structs.ACLPolicySetRequest{
   200  		Names: fetch,
   201  		QueryOptions: structs.QueryOptions{
   202  			Region:     c.Region(),
   203  			AuthToken:  secretID,
   204  			AllowStale: true,
   205  		},
   206  	}
   207  	var resp structs.ACLPolicySetResponse
   208  	if err := c.RPC("ACL.GetPolicies", &req, &resp); err != nil {
   209  		// If we encounter an error but have cached policies, mask the error and extend the cache
   210  		if len(missing) == 0 {
   211  			c.logger.Warn("failed to resolve policies, using expired cached value", "error", err)
   212  			out = append(out, expired...)
   213  			return out, nil
   214  		}
   215  		return nil, err
   216  	}
   217  
   218  	// Handle each output
   219  	for _, policy := range resp.Policies {
   220  		c.policyCache.Add(policy.Name, &cachedACLValue{
   221  			Policy:    policy,
   222  			CacheTime: time.Now(),
   223  		})
   224  		out = append(out, policy)
   225  	}
   226  
   227  	// Return the valid policies
   228  	return out, nil
   229  }