372bb06c83
I wrote the docs under this assumption but completely forgot to actually enforce it.
177 lines
5.3 KiB
Go
177 lines
5.3 KiB
Go
package consul
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/consul/agent/consul/authmethod"
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
"github.com/hashicorp/go-bexpr"
|
|
|
|
// register this as a builtin auth method
|
|
_ "github.com/hashicorp/consul/agent/consul/authmethod/kubeauth"
|
|
)
|
|
|
|
type authMethodValidatorEntry struct {
|
|
Validator authmethod.Validator
|
|
ModifyIndex uint64 // the raft index when this last changed
|
|
}
|
|
|
|
// loadAuthMethodValidator returns an authmethod.Validator for the given auth
|
|
// method configuration. If the cache is up to date as-of the provided index
|
|
// then the cached version is returned, otherwise a new validator is created
|
|
// and cached.
|
|
func (s *Server) loadAuthMethodValidator(idx uint64, method *structs.ACLAuthMethod) (authmethod.Validator, error) {
|
|
if prevIdx, v, ok := s.getCachedAuthMethodValidator(method.Name); ok && idx <= prevIdx {
|
|
return v, nil
|
|
}
|
|
|
|
v, err := authmethod.NewValidator(method)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("auth method validator for %q could not be initialized: %v", method.Name, err)
|
|
}
|
|
|
|
v = s.getOrReplaceAuthMethodValidator(method.Name, idx, v)
|
|
|
|
return v, nil
|
|
}
|
|
|
|
// getCachedAuthMethodValidator returns an AuthMethodValidator for
|
|
// the given name exclusively from the cache. If one is not found in the cache
|
|
// nil is returned.
|
|
func (s *Server) getCachedAuthMethodValidator(name string) (uint64, authmethod.Validator, bool) {
|
|
s.aclAuthMethodValidatorLock.RLock()
|
|
defer s.aclAuthMethodValidatorLock.RUnlock()
|
|
|
|
if s.aclAuthMethodValidators != nil {
|
|
v, ok := s.aclAuthMethodValidators[name]
|
|
if ok {
|
|
return v.ModifyIndex, v.Validator, true
|
|
}
|
|
}
|
|
return 0, nil, false
|
|
}
|
|
|
|
// getOrReplaceAuthMethodValidator updates the cached validator with the
|
|
// provided one UNLESS it has been updated by another goroutine in which case
|
|
// the updated one is returned.
|
|
func (s *Server) getOrReplaceAuthMethodValidator(name string, idx uint64, v authmethod.Validator) authmethod.Validator {
|
|
s.aclAuthMethodValidatorLock.Lock()
|
|
defer s.aclAuthMethodValidatorLock.Unlock()
|
|
|
|
if s.aclAuthMethodValidators == nil {
|
|
s.aclAuthMethodValidators = make(map[string]*authMethodValidatorEntry)
|
|
}
|
|
|
|
prev, ok := s.aclAuthMethodValidators[name]
|
|
if ok {
|
|
if prev.ModifyIndex >= idx {
|
|
return prev.Validator
|
|
}
|
|
}
|
|
|
|
s.logger.Printf("[DEBUG] acl: updating cached auth method validator for %q", name)
|
|
|
|
s.aclAuthMethodValidators[name] = &authMethodValidatorEntry{
|
|
Validator: v,
|
|
ModifyIndex: idx,
|
|
}
|
|
return v
|
|
}
|
|
|
|
// purgeAuthMethodValidators resets the cache of validators.
|
|
func (s *Server) purgeAuthMethodValidators() {
|
|
s.aclAuthMethodValidatorLock.Lock()
|
|
s.aclAuthMethodValidators = make(map[string]*authMethodValidatorEntry)
|
|
s.aclAuthMethodValidatorLock.Unlock()
|
|
}
|
|
|
|
// evaluateRoleBindings evaluates all current binding rules associated with the
|
|
// given auth method against the verified data returned from the authentication
|
|
// process.
|
|
//
|
|
// A list of role links and service identities are returned.
|
|
func (s *Server) evaluateRoleBindings(
|
|
validator authmethod.Validator,
|
|
verifiedFields map[string]string,
|
|
) ([]*structs.ACLServiceIdentity, []structs.ACLTokenRoleLink, error) {
|
|
// Only fetch rules that are relevant for this method.
|
|
_, rules, err := s.fsm.State().ACLBindingRuleList(nil, validator.Name())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
} else if len(rules) == 0 {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
// Convert the fields into something suitable for go-bexpr.
|
|
selectableVars := validator.MakeFieldMapSelectable(verifiedFields)
|
|
|
|
// Find all binding rules that match the provided fields.
|
|
var matchingRules []*structs.ACLBindingRule
|
|
for _, rule := range rules {
|
|
if doesBindingRuleMatch(rule, selectableVars) {
|
|
matchingRules = append(matchingRules, rule)
|
|
}
|
|
}
|
|
if len(matchingRules) == 0 {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
// For all matching rules compute the attributes of a token.
|
|
var (
|
|
roleLinks []structs.ACLTokenRoleLink
|
|
serviceIdentities []*structs.ACLServiceIdentity
|
|
)
|
|
for _, rule := range matchingRules {
|
|
bindName, valid, err := computeBindingRuleBindName(rule.BindType, rule.BindName, verifiedFields)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("cannot compute %q bind name for bind target: %v", rule.BindType, err)
|
|
} else if !valid {
|
|
return nil, nil, fmt.Errorf("computed %q bind name for bind target is invalid: %q", rule.BindType, bindName)
|
|
}
|
|
|
|
switch rule.BindType {
|
|
case structs.BindingRuleBindTypeService:
|
|
serviceIdentities = append(serviceIdentities, &structs.ACLServiceIdentity{
|
|
ServiceName: bindName,
|
|
})
|
|
|
|
case structs.BindingRuleBindTypeRole:
|
|
_, role, err := s.fsm.State().ACLRoleGetByName(nil, bindName)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if role != nil {
|
|
roleLinks = append(roleLinks, structs.ACLTokenRoleLink{
|
|
ID: role.ID,
|
|
})
|
|
}
|
|
|
|
default:
|
|
// skip unknown bind type; don't grant privileges
|
|
}
|
|
}
|
|
|
|
return serviceIdentities, roleLinks, nil
|
|
}
|
|
|
|
// doesBindingRuleMatch checks that a single binding rule matches the provided
|
|
// vars.
|
|
func doesBindingRuleMatch(rule *structs.ACLBindingRule, selectableVars interface{}) bool {
|
|
if rule.Selector == "" {
|
|
return true // catch-all
|
|
}
|
|
|
|
eval, err := bexpr.CreateEvaluatorForType(rule.Selector, nil, selectableVars)
|
|
if err != nil {
|
|
return false // fails to match if selector is invalid
|
|
}
|
|
|
|
result, err := eval.Evaluate(selectableVars)
|
|
if err != nil {
|
|
return false // fails to match if evaluation fails
|
|
}
|
|
|
|
return result
|
|
}
|