485d216ab3
The OIDC provider cache is used by the RPC handler as the OIDC implementation keeps long lived processes running. These process include connections to the remote OIDC provider. The Callback server is used by the CLI and starts when the login command is triggered. This callback server includes success HTML which is displayed when the user successfully logs into the remote OIDC provider.
204 lines
5.4 KiB
Go
204 lines
5.4 KiB
Go
package oidc
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/go-bexpr"
|
|
"github.com/hashicorp/go-memdb"
|
|
"github.com/hashicorp/hil"
|
|
"github.com/hashicorp/hil/ast"
|
|
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
)
|
|
|
|
// Binder is responsible for collecting the ACL roles and policies to be
|
|
// assigned to a token generated as a result of "logging in" via an auth method.
|
|
//
|
|
// It does so by applying the auth method's configured binding rules.
|
|
type Binder struct {
|
|
store BinderStateStore
|
|
}
|
|
|
|
// NewBinder creates a Binder with the given state store.
|
|
func NewBinder(store BinderStateStore) *Binder {
|
|
return &Binder{store}
|
|
}
|
|
|
|
// BinderStateStore is the subset of state store methods used by the binder.
|
|
type BinderStateStore interface {
|
|
GetACLBindingRulesByAuthMethod(ws memdb.WatchSet, authMethod string) (memdb.ResultIterator, error)
|
|
GetACLRoleByName(ws memdb.WatchSet, roleName string) (*structs.ACLRole, error)
|
|
ACLPolicyByName(ws memdb.WatchSet, name string) (*structs.ACLPolicy, error)
|
|
}
|
|
|
|
// Bindings contains the ACL roles and policies to be assigned to the created
|
|
// token.
|
|
type Bindings struct {
|
|
Roles []*structs.ACLTokenRoleLink
|
|
Policies []string
|
|
}
|
|
|
|
// None indicates that the resulting bindings would not give the created token
|
|
// access to any resources.
|
|
func (b *Bindings) None() bool {
|
|
if b == nil {
|
|
return true
|
|
}
|
|
|
|
return len(b.Policies) == 0 && len(b.Roles) == 0
|
|
}
|
|
|
|
// Bind collects the ACL roles and policies to be assigned to the created token.
|
|
func (b *Binder) Bind(authMethod *structs.ACLAuthMethod, identity *Identity) (*Bindings, error) {
|
|
var (
|
|
bindings Bindings
|
|
err error
|
|
)
|
|
|
|
// Load the auth method's binding rules.
|
|
rulesIterator, err := b.store.GetACLBindingRulesByAuthMethod(nil, authMethod.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Find the rules with selectors that match the identity's fields.
|
|
matchingRules := []*structs.ACLBindingRule{}
|
|
for {
|
|
raw := rulesIterator.Next()
|
|
if raw == nil {
|
|
break
|
|
}
|
|
rule := raw.(*structs.ACLBindingRule)
|
|
if doesSelectorMatch(rule.Selector, identity.Claims) {
|
|
matchingRules = append(matchingRules, rule)
|
|
}
|
|
}
|
|
if len(matchingRules) == 0 {
|
|
return &bindings, nil
|
|
}
|
|
|
|
// Compute role or policy names by interpolating the identity's claim
|
|
// mappings into the rule BindName templates.
|
|
for _, rule := range matchingRules {
|
|
bindName, valid, err := computeBindName(rule.BindType, rule.BindName, identity.ClaimMappings)
|
|
switch {
|
|
case err != nil:
|
|
return nil, fmt.Errorf("cannot compute %q bind name for bind target: %w", rule.BindType, err)
|
|
case !valid:
|
|
return nil, fmt.Errorf("computed %q bind name for bind target is invalid: %q", rule.BindType, bindName)
|
|
}
|
|
|
|
switch rule.BindType {
|
|
case structs.ACLBindingRuleBindTypeRole:
|
|
role, err := b.store.GetACLRoleByName(nil, bindName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if role != nil {
|
|
bindings.Roles = append(bindings.Roles, &structs.ACLTokenRoleLink{
|
|
ID: role.ID,
|
|
})
|
|
}
|
|
case structs.ACLBindingRuleBindTypePolicy:
|
|
policy, err := b.store.ACLPolicyByName(nil, bindName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if policy != nil {
|
|
bindings.Policies = append(bindings.Policies, policy.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
return &bindings, nil
|
|
}
|
|
|
|
// computeBindName processes the HIL for the provided bind type+name using the
|
|
// projected variables.
|
|
//
|
|
// - If the HIL is invalid ("", false, AN_ERROR) is returned.
|
|
// - If the computed name is not valid for the type ("INVALID_NAME", false, nil) is returned.
|
|
// - If the computed name is valid for the type ("VALID_NAME", true, nil) is returned.
|
|
func computeBindName(bindType, bindName string, claimMappings map[string]string) (string, bool, error) {
|
|
bindName, err := interpolateHIL(bindName, claimMappings, true)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
|
|
var valid bool
|
|
switch bindType {
|
|
case structs.ACLBindingRuleBindTypePolicy:
|
|
valid = structs.ValidPolicyName.MatchString(bindName)
|
|
case structs.ACLBindingRuleBindTypeRole:
|
|
valid = structs.ValidACLRoleName.MatchString(bindName)
|
|
default:
|
|
return "", false, fmt.Errorf("unknown binding rule bind type: %s", bindType)
|
|
}
|
|
|
|
return bindName, valid, nil
|
|
}
|
|
|
|
// doesSelectorMatch checks that a single selector matches the provided vars.
|
|
func doesSelectorMatch(selector string, selectableVars interface{}) bool {
|
|
if selector == "" {
|
|
return true // catch-all
|
|
}
|
|
|
|
eval, err := bexpr.CreateEvaluator(selector)
|
|
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
|
|
}
|
|
|
|
// interpolateHIL processes the string as if it were HIL and interpolates only
|
|
// the provided string->string map as possible variables.
|
|
func interpolateHIL(s string, vars map[string]string, lowercase bool) (string, error) {
|
|
if !strings.Contains(s, "${") {
|
|
// Skip going to the trouble of parsing something that has no HIL.
|
|
return s, nil
|
|
}
|
|
|
|
tree, err := hil.Parse(s)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
vm := make(map[string]ast.Variable)
|
|
for k, v := range vars {
|
|
if lowercase {
|
|
v = strings.ToLower(v)
|
|
}
|
|
vm[k] = ast.Variable{
|
|
Type: ast.TypeString,
|
|
Value: v,
|
|
}
|
|
}
|
|
|
|
config := &hil.EvalConfig{
|
|
GlobalScope: &ast.BasicScope{
|
|
VarMap: vm,
|
|
},
|
|
}
|
|
|
|
result, err := hil.Eval(tree, config)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if result.Type != hil.TypeString {
|
|
return "", fmt.Errorf("generated unexpected hil type: %s", result.Type)
|
|
}
|
|
|
|
return result.Value.(string), nil
|
|
}
|