209 lines
5.5 KiB
Go
209 lines
5.5 KiB
Go
// 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
|
|
}
|