// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package authmethod import ( "context" "fmt" "sort" "sync" "github.com/hashicorp/go-hclog" "github.com/mitchellh/mapstructure" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/structs" ) type Cache interface { // GetValidator retrieves the Validator from the cache. // It returns the modify index of struct that the validator was created from, // the validator and a boolean indicating whether the value was found GetValidator(method *structs.ACLAuthMethod) (uint64, Validator, bool) // PutValidatorIfNewer inserts a new validator into the cache if the index is greater // than the modify index of any existing entry in the cache. This method will return // the newest validator which may or may not be the one from the method parameter PutValidatorIfNewer(method *structs.ACLAuthMethod, validator Validator, idx uint64) Validator // Purge removes all cached validators Purge() } type ValidatorFactory func(logger hclog.Logger, method *structs.ACLAuthMethod) (Validator, error) type Validator interface { // Name returns the name of the auth method backing this validator. Name() string // NewIdentity creates a blank identity populated with empty values. NewIdentity() *Identity // ValidateLogin takes raw user-provided auth method metadata and ensures // it is reasonable, provably correct, and currently valid. Relevant identifying // data is extracted and returned for immediate use by the role binding // process. // // Depending upon the method, it may make sense to use these calls to // continue to extend the life of the underlying token. // // Returns auth method specific metadata suitable for the Role Binding // process as well as the desired enterprise meta for the token to be // created. ValidateLogin(ctx context.Context, loginToken string) (*Identity, error) // Stop should be called to cease any background activity and free up // resources. Stop() } type Identity struct { // SelectableFields is the format of this Identity suitable for selection // with a binding rule. SelectableFields interface{} // ProjectedVars is the format of this Identity suitable for interpolation // in a bind name within a binding rule. ProjectedVars map[string]string *acl.EnterpriseMeta } // ProjectedVarNames returns just the keyspace of the ProjectedVars map. func (i *Identity) ProjectedVarNames() []string { v := make([]string, 0, len(i.ProjectedVars)) for k := range i.ProjectedVars { v = append(v, k) } return v } var ( typesMu sync.RWMutex types = make(map[string]ValidatorFactory) ) // Register makes an auth method with the given type available for use. If // Register is called twice with the same name or if validator is nil, it // panics. func Register(name string, factory ValidatorFactory) { typesMu.Lock() defer typesMu.Unlock() if factory == nil { panic("authmethod: Register factory is nil for type " + name) } if _, dup := types[name]; dup { panic("authmethod: Register called twice for type " + name) } types[name] = factory } func IsRegisteredType(typeName string) bool { typesMu.RLock() _, ok := types[typeName] typesMu.RUnlock() return ok } type authMethodValidatorEntry struct { Validator Validator ModifyIndex uint64 } // authMethodCache is an non-thread-safe cache that maps ACLAuthMethods to their Validators type authMethodCache struct { entries map[string]*authMethodValidatorEntry } func newCache() Cache { c := &authMethodCache{} c.init() return c } func (c *authMethodCache) init() { c.Purge() } func (c *authMethodCache) GetValidator(method *structs.ACLAuthMethod) (uint64, Validator, bool) { entry, ok := c.entries[method.Name] if ok { return entry.ModifyIndex, entry.Validator, true } return 0, nil, false } func (c *authMethodCache) PutValidatorIfNewer(method *structs.ACLAuthMethod, validator Validator, idx uint64) Validator { prev, ok := c.entries[method.Name] if ok { if prev.ModifyIndex >= idx { return prev.Validator } prev.Validator.Stop() } c.entries[method.Name] = &authMethodValidatorEntry{ Validator: validator, ModifyIndex: idx, } return validator } func (c *authMethodCache) Purge() { for _, entry := range c.entries { entry.Validator.Stop() } c.entries = make(map[string]*authMethodValidatorEntry) } // NewValidator instantiates a new Validator for the given auth method // configuration. If no auth method is registered with the provided type an // error is returned. func NewValidator(logger hclog.Logger, method *structs.ACLAuthMethod) (Validator, error) { typesMu.RLock() factory, ok := types[method.Type] typesMu.RUnlock() if !ok { return nil, fmt.Errorf("no auth method registered with type: %s", method.Type) } logger = logger.Named("authmethod").With("type", method.Type, "name", method.Name) return factory(logger, method) } // Types returns a sorted list of the names of the registered types. func Types() []string { typesMu.RLock() defer typesMu.RUnlock() var list []string for name := range types { list = append(list, name) } sort.Strings(list) return list } // ParseConfig parses the config block for a auth method. func ParseConfig(rawConfig map[string]interface{}, out interface{}) error { decodeConf := &mapstructure.DecoderConfig{ Result: out, WeaklyTypedInput: true, ErrorUnused: true, } decoder, err := mapstructure.NewDecoder(decodeConf) if err != nil { return err } if err := decoder.Decode(rawConfig); err != nil { return fmt.Errorf("error decoding config: %s", err) } return nil }