167 lines
4.1 KiB
Go
167 lines
4.1 KiB
Go
|
package testauth
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"sync"
|
||
|
|
||
|
"github.com/hashicorp/consul/acl"
|
||
|
"github.com/hashicorp/consul/agent/consul/authmethod"
|
||
|
"github.com/hashicorp/consul/agent/structs"
|
||
|
"github.com/hashicorp/go-uuid"
|
||
|
)
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
func newValidator(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 }
|
||
|
|
||
|
// ValidateLogin takes raw user-provided auth method metadata and ensures it is
|
||
|
// sane, 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(loginToken string) (map[string]string, error) {
|
||
|
fields, valid := GetSessionToken(v.config.SessionID, loginToken)
|
||
|
if !valid {
|
||
|
return nil, acl.ErrNotFound
|
||
|
}
|
||
|
|
||
|
return fields, nil
|
||
|
}
|
||
|
|
||
|
func (v *Validator) AvailableFields() []string { return availableFields }
|
||
|
|
||
|
const (
|
||
|
serviceAccountNamespaceField = "serviceaccount.namespace"
|
||
|
serviceAccountNameField = "serviceaccount.name"
|
||
|
serviceAccountUIDField = "serviceaccount.uid"
|
||
|
)
|
||
|
|
||
|
var availableFields = []string{
|
||
|
serviceAccountNamespaceField,
|
||
|
serviceAccountNameField,
|
||
|
serviceAccountUIDField,
|
||
|
}
|
||
|
|
||
|
// MakeFieldMapSelectable converts a field map as returned by ValidateLogin
|
||
|
// into a structure suitable for selection with a binding rule.
|
||
|
func (v *Validator) MakeFieldMapSelectable(fieldMap map[string]string) interface{} {
|
||
|
return &selectableVars{
|
||
|
ServiceAccount: selectableServiceAccount{
|
||
|
Namespace: fieldMap[serviceAccountNamespaceField],
|
||
|
Name: fieldMap[serviceAccountNameField],
|
||
|
UID: fieldMap[serviceAccountUIDField],
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type selectableVars struct {
|
||
|
ServiceAccount selectableServiceAccount `bexpr:"serviceaccount"`
|
||
|
}
|
||
|
|
||
|
type selectableServiceAccount struct {
|
||
|
Namespace string `bexpr:"namespace"`
|
||
|
Name string `bexpr:"name"`
|
||
|
UID string `bexpr:"uid"`
|
||
|
}
|