open-consul/agent/consul/authmethod/testauth/testing.go

190 lines
4.5 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package testauth
import (
"context"
"fmt"
"sync"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/consul/authmethod"
"github.com/hashicorp/consul/agent/structs"
)
func init() {
authmethod.Register("testing", newValidator)
}
var (
tokenDatabaseMu sync.Mutex
tokenDatabase map[string]map[string]map[string]string // session => token => fieldmap
)
func StartSession() string {
sessionID, err := uuid.GenerateUUID()
if err != nil {
panic(err)
}
return sessionID
}
func ResetSession(sessionID string) {
tokenDatabaseMu.Lock()
defer tokenDatabaseMu.Unlock()
if tokenDatabase != nil {
delete(tokenDatabase, sessionID)
}
}
func InstallSessionToken(sessionID string, token string, namespace, name, uid string) {
fields := map[string]string{
serviceAccountNamespaceField: namespace,
serviceAccountNameField: name,
serviceAccountUIDField: uid,
}
tokenDatabaseMu.Lock()
defer tokenDatabaseMu.Unlock()
if tokenDatabase == nil {
tokenDatabase = make(map[string]map[string]map[string]string)
}
sdb, ok := tokenDatabase[sessionID]
if !ok {
sdb = make(map[string]map[string]string)
tokenDatabase[sessionID] = sdb
}
sdb[token] = fields
}
func GetSessionToken(sessionID string, token string) (map[string]string, bool) {
tokenDatabaseMu.Lock()
defer tokenDatabaseMu.Unlock()
if tokenDatabase == nil {
return nil, false
}
sdb, ok := tokenDatabase[sessionID]
if !ok {
return nil, false
}
fields, ok := sdb[token]
if !ok {
return nil, false
}
fmCopy := make(map[string]string)
for k, v := range fields {
fmCopy[k] = v
}
return fmCopy, true
}
type Config struct {
SessionID string // unique identifier for this set of tokens in the database
Data map[string]string `json:",omitempty"` // random data for testing
enterpriseConfig `mapstructure:",squash"`
}
func newValidator(logger hclog.Logger, method *structs.ACLAuthMethod) (authmethod.Validator, error) {
if method.Type != "testing" {
return nil, fmt.Errorf("%q is not a testing auth method", method.Name)
}
var config Config
if err := authmethod.ParseConfig(method.Config, &config); err != nil {
return nil, err
}
if config.SessionID == "" {
// If you don't explicitly create one, we create a random one but you
// won't have access to it. Useful if you are testing everything EXCEPT
// ValidateToken().
config.SessionID = StartSession()
}
return &Validator{
name: method.Name,
config: &config,
}, nil
}
type Validator struct {
name string
config *Config
}
func (v *Validator) Name() string { return v.name }
func (v *Validator) Stop() {}
// 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.
func (v *Validator) ValidateLogin(ctx context.Context, loginToken string) (*authmethod.Identity, error) {
fields, valid := GetSessionToken(v.config.SessionID, loginToken)
if !valid {
return nil, acl.ErrNotFound
}
id := v.NewIdentity()
id.SelectableFields = &selectableVars{
ServiceAccount: selectableServiceAccount{
Namespace: fields[serviceAccountNamespaceField],
Name: fields[serviceAccountNameField],
UID: fields[serviceAccountUIDField],
},
}
for k, val := range fields {
id.ProjectedVars[k] = val
}
id.EnterpriseMeta = v.testAuthEntMetaFromFields(fields)
return id, nil
}
func (v *Validator) NewIdentity() *authmethod.Identity {
id := &authmethod.Identity{
SelectableFields: &selectableVars{},
ProjectedVars: map[string]string{},
}
for _, f := range availableFields {
id.ProjectedVars[f] = ""
}
return id
}
const (
serviceAccountNamespaceField = "serviceaccount.namespace"
serviceAccountNameField = "serviceaccount.name"
serviceAccountUIDField = "serviceaccount.uid"
)
var availableFields = []string{
serviceAccountNamespaceField,
serviceAccountNameField,
serviceAccountUIDField,
}
type selectableVars struct {
ServiceAccount selectableServiceAccount `bexpr:"serviceaccount"`
}
type selectableServiceAccount struct {
Namespace string `bexpr:"namespace"`
Name string `bexpr:"name"`
UID string `bexpr:"uid"`
}