github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/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) resolveTokenAndACL(secretID string) (*acl.ACL, *structs.ACLToken, error) {
    78  	// Fast-path if ACLs are disabled
    79  	if !c.config.ACLEnabled {
    80  		return nil, nil, nil
    81  	}
    82  	defer metrics.MeasureSince([]string{"client", "acl", "resolve_token"}, time.Now())
    83  
    84  	// Resolve the token value
    85  	token, err := c.resolveTokenValue(secretID)
    86  	if err != nil {
    87  		return nil, nil, err
    88  	}
    89  	if token == nil {
    90  		return nil, nil, structs.ErrTokenNotFound
    91  	}
    92  
    93  	// Check if this is a management token
    94  	if token.Type == structs.ACLManagementToken {
    95  		return acl.ManagementACL, token, nil
    96  	}
    97  
    98  	// Resolve the policies
    99  	policies, err := c.resolvePolicies(token.SecretID, token.Policies)
   100  	if err != nil {
   101  		return nil, nil, err
   102  	}
   103  
   104  	// Resolve the ACL object
   105  	aclObj, err := structs.CompileACLObject(c.aclCache, policies)
   106  	if err != nil {
   107  		return nil, nil, err
   108  	}
   109  	return aclObj, token, nil
   110  }
   111  
   112  // resolveTokenValue is used to translate a secret ID into an ACL token with caching
   113  // We use a local cache up to the TTL limit, and then resolve via a server. If we cannot
   114  // reach a server, but have a cached value we extend the TTL to gracefully handle outages.
   115  func (c *Client) resolveTokenValue(secretID string) (*structs.ACLToken, error) {
   116  	// Hot-path the anonymous token
   117  	if secretID == "" {
   118  		return structs.AnonymousACLToken, nil
   119  	}
   120  
   121  	// Lookup the token in the cache
   122  	raw, ok := c.tokenCache.Get(secretID)
   123  	if ok {
   124  		cached := raw.(*cachedACLValue)
   125  		if cached.Age() <= c.config.ACLTokenTTL {
   126  			return cached.Token, nil
   127  		}
   128  	}
   129  
   130  	// Lookup the token
   131  	req := structs.ResolveACLTokenRequest{
   132  		SecretID: secretID,
   133  		QueryOptions: structs.QueryOptions{
   134  			Region:     c.Region(),
   135  			AllowStale: true,
   136  		},
   137  	}
   138  	var resp structs.ResolveACLTokenResponse
   139  	if err := c.RPC("ACL.ResolveToken", &req, &resp); err != nil {
   140  		// If we encounter an error but have a cached value, mask the error and extend the cache
   141  		if ok {
   142  			c.logger.Warn("failed to resolve token, using expired cached value", "error", err)
   143  			cached := raw.(*cachedACLValue)
   144  			return cached.Token, nil
   145  		}
   146  		return nil, err
   147  	}
   148  
   149  	// Cache the response (positive or negative)
   150  	c.tokenCache.Add(secretID, &cachedACLValue{
   151  		Token:     resp.Token,
   152  		CacheTime: time.Now(),
   153  	})
   154  	return resp.Token, nil
   155  }
   156  
   157  // resolvePolicies is used to translate a set of named ACL policies into the objects.
   158  // We cache the policies locally, and fault them from a server as necessary. Policies
   159  // are cached for a TTL, and then refreshed. If a server cannot be reached, the cache TTL
   160  // will be ignored to gracefully handle outages.
   161  func (c *Client) resolvePolicies(secretID string, policies []string) ([]*structs.ACLPolicy, error) {
   162  	var out []*structs.ACLPolicy
   163  	var expired []*structs.ACLPolicy
   164  	var missing []string
   165  
   166  	// Scan the cache for each policy
   167  	for _, policyName := range policies {
   168  		// Lookup the policy in the cache
   169  		raw, ok := c.policyCache.Get(policyName)
   170  		if !ok {
   171  			missing = append(missing, policyName)
   172  			continue
   173  		}
   174  
   175  		// Check if the cached value is valid or expired
   176  		cached := raw.(*cachedACLValue)
   177  		if cached.Age() <= c.config.ACLPolicyTTL {
   178  			out = append(out, cached.Policy)
   179  		} else {
   180  			expired = append(expired, cached.Policy)
   181  		}
   182  	}
   183  
   184  	// Hot-path if we have no missing or expired policies
   185  	if len(missing)+len(expired) == 0 {
   186  		return out, nil
   187  	}
   188  
   189  	// Lookup the missing and expired policies
   190  	fetch := missing
   191  	for _, p := range expired {
   192  		fetch = append(fetch, p.Name)
   193  	}
   194  	req := structs.ACLPolicySetRequest{
   195  		Names: fetch,
   196  		QueryOptions: structs.QueryOptions{
   197  			Region:     c.Region(),
   198  			AuthToken:  secretID,
   199  			AllowStale: true,
   200  		},
   201  	}
   202  	var resp structs.ACLPolicySetResponse
   203  	if err := c.RPC("ACL.GetPolicies", &req, &resp); err != nil {
   204  		// If we encounter an error but have cached policies, mask the error and extend the cache
   205  		if len(missing) == 0 {
   206  			c.logger.Warn("failed to resolve policies, using expired cached value", "error", err)
   207  			out = append(out, expired...)
   208  			return out, nil
   209  		}
   210  		return nil, err
   211  	}
   212  
   213  	// Handle each output
   214  	for _, policy := range resp.Policies {
   215  		c.policyCache.Add(policy.Name, &cachedACLValue{
   216  			Policy:    policy,
   217  			CacheTime: time.Now(),
   218  		})
   219  		out = append(out, policy)
   220  	}
   221  
   222  	// Return the valid policies
   223  	return out, nil
   224  }