AuthMethod updates to support alternate namespace logins (#7029)
This commit is contained in:
parent
cc8f580ddd
commit
c8294b8595
|
@ -358,6 +358,9 @@ func (s *HTTPServer) ACLTokenList(resp http.ResponseWriter, req *http.Request) (
|
||||||
args.Policy = req.URL.Query().Get("policy")
|
args.Policy = req.URL.Query().Get("policy")
|
||||||
args.Role = req.URL.Query().Get("role")
|
args.Role = req.URL.Query().Get("role")
|
||||||
args.AuthMethod = req.URL.Query().Get("authmethod")
|
args.AuthMethod = req.URL.Query().Get("authmethod")
|
||||||
|
if err := parseACLAuthMethodEnterpriseMeta(req, &args.ACLAuthMethodEnterpriseMeta); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var out structs.ACLTokenListResponse
|
var out structs.ACLTokenListResponse
|
||||||
defer setMeta(resp, &out.QueryMeta)
|
defer setMeta(resp, &out.QueryMeta)
|
||||||
|
|
|
@ -1405,7 +1405,6 @@ func (s *HTTPServer) AgentHost(resp http.ResponseWriter, req *http.Request) (int
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (namespaces) - pass through a real ent authz ctx
|
|
||||||
if rule != nil && rule.OperatorRead(nil) != acl.Allow {
|
if rule != nil && rule.OperatorRead(nil) != acl.Allow {
|
||||||
return nil, acl.ErrPermissionDenied
|
return nil, acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,10 +43,11 @@ func (s *Server) loadAuthMethodValidator(idx uint64, method *structs.ACLAuthMeth
|
||||||
func (s *Server) evaluateRoleBindings(
|
func (s *Server) evaluateRoleBindings(
|
||||||
validator authmethod.Validator,
|
validator authmethod.Validator,
|
||||||
verifiedFields map[string]string,
|
verifiedFields map[string]string,
|
||||||
entMeta *structs.EnterpriseMeta,
|
methodMeta *structs.EnterpriseMeta,
|
||||||
|
targetMeta *structs.EnterpriseMeta,
|
||||||
) ([]*structs.ACLServiceIdentity, []structs.ACLTokenRoleLink, error) {
|
) ([]*structs.ACLServiceIdentity, []structs.ACLTokenRoleLink, error) {
|
||||||
// Only fetch rules that are relevant for this method.
|
// Only fetch rules that are relevant for this method.
|
||||||
_, rules, err := s.fsm.State().ACLBindingRuleList(nil, validator.Name(), entMeta)
|
_, rules, err := s.fsm.State().ACLBindingRuleList(nil, validator.Name(), methodMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
} else if len(rules) == 0 {
|
} else if len(rules) == 0 {
|
||||||
|
@ -87,7 +88,7 @@ func (s *Server) evaluateRoleBindings(
|
||||||
})
|
})
|
||||||
|
|
||||||
case structs.BindingRuleBindTypeRole:
|
case structs.BindingRuleBindTypeRole:
|
||||||
_, role, err := s.fsm.State().ACLRoleGetByName(nil, bindName, &rule.EnterpriseMeta)
|
_, role, err := s.fsm.State().ACLRoleGetByName(nil, bindName, targetMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -828,9 +828,15 @@ func (a *ACL) TokenList(args *structs.ACLTokenListRequest, reply *structs.ACLTok
|
||||||
return acl.ErrPermissionDenied
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var methodMeta *structs.EnterpriseMeta
|
||||||
|
if args.AuthMethod != "" {
|
||||||
|
methodMeta = args.ACLAuthMethodEnterpriseMeta.ToEnterpriseMeta()
|
||||||
|
methodMeta.Merge(&args.EnterpriseMeta)
|
||||||
|
}
|
||||||
|
|
||||||
return a.srv.blockingQuery(&args.QueryOptions, &reply.QueryMeta,
|
return a.srv.blockingQuery(&args.QueryOptions, &reply.QueryMeta,
|
||||||
func(ws memdb.WatchSet, state *state.Store) error {
|
func(ws memdb.WatchSet, state *state.Store) error {
|
||||||
index, tokens, err := state.ACLTokenList(ws, args.IncludeLocal, args.IncludeGlobal, args.Policy, args.Role, args.AuthMethod, &args.EnterpriseMeta)
|
index, tokens, err := state.ACLTokenList(ws, args.IncludeLocal, args.IncludeGlobal, args.Policy, args.Role, args.AuthMethod, methodMeta, &args.EnterpriseMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -2221,13 +2227,16 @@ func (a *ACL) Login(args *structs.ACLLoginRequest, reply *structs.ACLToken) erro
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Send args.Data.BearerToken to method validator and get back a fields map
|
// 2. Send args.Data.BearerToken to method validator and get back a fields map
|
||||||
verifiedFields, err := validator.ValidateLogin(auth.BearerToken)
|
verifiedFields, desiredMeta, err := validator.ValidateLogin(auth.BearerToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This always will return a valid pointer
|
||||||
|
targetMeta := method.TargetEnterpriseMeta(desiredMeta)
|
||||||
|
|
||||||
// 3. send map through role bindings
|
// 3. send map through role bindings
|
||||||
serviceIdentities, roleLinks, err := a.srv.evaluateRoleBindings(validator, verifiedFields, &auth.EnterpriseMeta)
|
serviceIdentities, roleLinks, err := a.srv.evaluateRoleBindings(validator, verifiedFields, &auth.EnterpriseMeta, targetMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -2256,11 +2265,13 @@ func (a *ACL) Login(args *structs.ACLLoginRequest, reply *structs.ACLToken) erro
|
||||||
AuthMethod: auth.AuthMethod,
|
AuthMethod: auth.AuthMethod,
|
||||||
ServiceIdentities: serviceIdentities,
|
ServiceIdentities: serviceIdentities,
|
||||||
Roles: roleLinks,
|
Roles: roleLinks,
|
||||||
EnterpriseMeta: auth.EnterpriseMeta,
|
EnterpriseMeta: *targetMeta,
|
||||||
},
|
},
|
||||||
WriteRequest: args.WriteRequest,
|
WriteRequest: args.WriteRequest,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createReq.ACLToken.ACLAuthMethodEnterpriseMeta.FillWithEnterpriseMeta(&auth.EnterpriseMeta)
|
||||||
|
|
||||||
// 5. return token information like a TokenCreate would
|
// 5. return token information like a TokenCreate would
|
||||||
err = a.tokenSetInternal(&createReq, reply, true)
|
err = a.tokenSetInternal(&createReq, reply, true)
|
||||||
|
|
||||||
|
|
|
@ -262,7 +262,7 @@ func (a *ACL) List(args *structs.DCSpecificRequest,
|
||||||
return a.srv.blockingQuery(&args.QueryOptions,
|
return a.srv.blockingQuery(&args.QueryOptions,
|
||||||
&reply.QueryMeta,
|
&reply.QueryMeta,
|
||||||
func(ws memdb.WatchSet, state *state.Store) error {
|
func(ws memdb.WatchSet, state *state.Store) error {
|
||||||
index, tokens, err := state.ACLTokenList(ws, false, true, "", "", "", nil)
|
index, tokens, err := state.ACLTokenList(ws, false, true, "", "", "", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,7 +138,7 @@ func reconcileLegacyACLs(local, remote structs.ACLs, lastRemoteIndex uint64) str
|
||||||
|
|
||||||
// FetchLocalACLs returns the ACLs in the local state store.
|
// FetchLocalACLs returns the ACLs in the local state store.
|
||||||
func (s *Server) fetchLocalLegacyACLs() (structs.ACLs, error) {
|
func (s *Server) fetchLocalLegacyACLs() (structs.ACLs, error) {
|
||||||
_, local, err := s.fsm.State().ACLTokenList(nil, false, true, "", "", "", nil)
|
_, local, err := s.fsm.State().ACLTokenList(nil, false, true, "", "", "", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -396,11 +396,11 @@ func TestACLReplication_LegacyTokens(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
checkSame := func() error {
|
checkSame := func() error {
|
||||||
index, remote, err := s1.fsm.State().ACLTokenList(nil, true, true, "", "", "", nil)
|
index, remote, err := s1.fsm.State().ACLTokenList(nil, true, true, "", "", "", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, local, err := s2.fsm.State().ACLTokenList(nil, true, true, "", "", "", nil)
|
_, local, err := s2.fsm.State().ACLTokenList(nil, true, true, "", "", "", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -351,9 +351,9 @@ func TestACLReplication_Tokens(t *testing.T) {
|
||||||
|
|
||||||
checkSame := func(t *retry.R) {
|
checkSame := func(t *retry.R) {
|
||||||
// only account for global tokens - local tokens shouldn't be replicated
|
// only account for global tokens - local tokens shouldn't be replicated
|
||||||
index, remote, err := s1.fsm.State().ACLTokenList(nil, false, true, "", "", "", nil)
|
index, remote, err := s1.fsm.State().ACLTokenList(nil, false, true, "", "", "", nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, local, err := s2.fsm.State().ACLTokenList(nil, false, true, "", "", "", nil)
|
_, local, err := s2.fsm.State().ACLTokenList(nil, false, true, "", "", "", nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Len(t, local, len(remote))
|
require.Len(t, local, len(remote))
|
||||||
|
@ -451,7 +451,7 @@ func TestACLReplication_Tokens(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// verify dc2 local tokens didn't get blown away
|
// verify dc2 local tokens didn't get blown away
|
||||||
_, local, err := s2.fsm.State().ACLTokenList(nil, true, false, "", "", "", nil)
|
_, local, err := s2.fsm.State().ACLTokenList(nil, true, false, "", "", "", nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, local, 50)
|
require.Len(t, local, 50)
|
||||||
|
|
||||||
|
@ -787,10 +787,10 @@ func TestACLReplication_AllTypes(t *testing.T) {
|
||||||
|
|
||||||
checkSameTokens := func(t *retry.R) {
|
checkSameTokens := func(t *retry.R) {
|
||||||
// only account for global tokens - local tokens shouldn't be replicated
|
// only account for global tokens - local tokens shouldn't be replicated
|
||||||
index, remote, err := s1.fsm.State().ACLTokenList(nil, false, true, "", "", "", nil)
|
index, remote, err := s1.fsm.State().ACLTokenList(nil, false, true, "", "", "", nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// Query for all of them, so that we can prove that no globals snuck in.
|
// Query for all of them, so that we can prove that no globals snuck in.
|
||||||
_, local, err := s2.fsm.State().ACLTokenList(nil, true, true, "", "", "", nil)
|
_, local, err := s2.fsm.State().ACLTokenList(nil, true, true, "", "", "", nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Len(t, remote, len(local))
|
require.Len(t, remote, len(local))
|
||||||
|
|
|
@ -34,7 +34,7 @@ func (r *aclTokenReplicator) FetchRemote(srv *Server, lastRemoteIndex uint64) (i
|
||||||
func (r *aclTokenReplicator) FetchLocal(srv *Server) (int, uint64, error) {
|
func (r *aclTokenReplicator) FetchLocal(srv *Server) (int, uint64, error) {
|
||||||
r.local = nil
|
r.local = nil
|
||||||
|
|
||||||
idx, local, err := srv.fsm.State().ACLTokenList(nil, false, true, "", "", "", srv.replicationEnterpriseMeta())
|
idx, local, err := srv.fsm.State().ACLTokenList(nil, false, true, "", "", "", nil, srv.replicationEnterpriseMeta())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,8 +39,9 @@ type Validator interface {
|
||||||
// continue to extend the life of the underlying token.
|
// continue to extend the life of the underlying token.
|
||||||
//
|
//
|
||||||
// Returns auth method specific metadata suitable for the Role Binding
|
// Returns auth method specific metadata suitable for the Role Binding
|
||||||
// process.
|
// process as well as the desired enterprise meta for the token to be
|
||||||
ValidateLogin(loginToken string) (map[string]string, error)
|
// created.
|
||||||
|
ValidateLogin(loginToken string) (map[string]string, *structs.EnterpriseMeta, error)
|
||||||
|
|
||||||
// AvailableFields returns a slice of all fields that are returned as a
|
// AvailableFields returns a slice of all fields that are returned as a
|
||||||
// result of ValidateLogin. These are valid fields for use in any
|
// result of ValidateLogin. These are valid fields for use in any
|
||||||
|
|
|
@ -50,6 +50,8 @@ type Config struct {
|
||||||
// other JWTs during login. It also must be able to read ServiceAccount
|
// other JWTs during login. It also must be able to read ServiceAccount
|
||||||
// annotations.
|
// annotations.
|
||||||
ServiceAccountJWT string `json:",omitempty"`
|
ServiceAccountJWT string `json:",omitempty"`
|
||||||
|
|
||||||
|
enterpriseConfig `mapstructure:",squash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validator is the wrapper around the relevant portions of the Kubernetes API
|
// Validator is the wrapper around the relevant portions of the Kubernetes API
|
||||||
|
@ -116,9 +118,9 @@ func NewValidator(method *structs.ACLAuthMethod) (*Validator, error) {
|
||||||
|
|
||||||
func (v *Validator) Name() string { return v.name }
|
func (v *Validator) Name() string { return v.name }
|
||||||
|
|
||||||
func (v *Validator) ValidateLogin(loginToken string) (map[string]string, error) {
|
func (v *Validator) ValidateLogin(loginToken string) (map[string]string, *structs.EnterpriseMeta, error) {
|
||||||
if _, err := jwt.ParseSigned(loginToken); err != nil {
|
if _, err := jwt.ParseSigned(loginToken); err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse and validate JWT: %v", err)
|
return nil, nil, fmt.Errorf("failed to parse and validate JWT: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check TokenReview for the bulk of the work.
|
// Check TokenReview for the bulk of the work.
|
||||||
|
@ -129,24 +131,24 @@ func (v *Validator) ValidateLogin(loginToken string) (map[string]string, error)
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
} else if trResp.Status.Error != "" {
|
} else if trResp.Status.Error != "" {
|
||||||
return nil, fmt.Errorf("lookup failed: %s", trResp.Status.Error)
|
return nil, nil, fmt.Errorf("lookup failed: %s", trResp.Status.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !trResp.Status.Authenticated {
|
if !trResp.Status.Authenticated {
|
||||||
return nil, errors.New("lookup failed: service account jwt not valid")
|
return nil, nil, errors.New("lookup failed: service account jwt not valid")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The username is of format: system:serviceaccount:(NAMESPACE):(SERVICEACCOUNT)
|
// The username is of format: system:serviceaccount:(NAMESPACE):(SERVICEACCOUNT)
|
||||||
parts := strings.Split(trResp.Status.User.Username, ":")
|
parts := strings.Split(trResp.Status.User.Username, ":")
|
||||||
if len(parts) != 4 {
|
if len(parts) != 4 {
|
||||||
return nil, errors.New("lookup failed: unexpected username format")
|
return nil, nil, errors.New("lookup failed: unexpected username format")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the user that comes back from token review is a service account
|
// Validate the user that comes back from token review is a service account
|
||||||
if parts[0] != "system" || parts[1] != "serviceaccount" {
|
if parts[0] != "system" || parts[1] != "serviceaccount" {
|
||||||
return nil, errors.New("lookup failed: username returned is not a service account")
|
return nil, nil, errors.New("lookup failed: username returned is not a service account")
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -158,7 +160,7 @@ func (v *Validator) ValidateLogin(loginToken string) (map[string]string, error)
|
||||||
// Check to see if there is an override name on the ServiceAccount object.
|
// Check to see if there is an override name on the ServiceAccount object.
|
||||||
sa, err := v.saGetter.ServiceAccounts(saNamespace).Get(saName, client_metav1.GetOptions{})
|
sa, err := v.saGetter.ServiceAccounts(saNamespace).Get(saName, client_metav1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("annotation lookup failed: %v", err)
|
return nil, nil, fmt.Errorf("annotation lookup failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
annotations := sa.GetObjectMeta().GetAnnotations()
|
annotations := sa.GetObjectMeta().GetAnnotations()
|
||||||
|
@ -166,11 +168,13 @@ func (v *Validator) ValidateLogin(loginToken string) (map[string]string, error)
|
||||||
saName = serviceNameOverride
|
saName = serviceNameOverride
|
||||||
}
|
}
|
||||||
|
|
||||||
return map[string]string{
|
fields := map[string]string{
|
||||||
serviceAccountNamespaceField: saNamespace,
|
serviceAccountNamespaceField: saNamespace,
|
||||||
serviceAccountNameField: saName,
|
serviceAccountNameField: saName,
|
||||||
serviceAccountUIDField: saUID,
|
serviceAccountUIDField: saUID,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
return fields, v.k8sEntMetaFromFields(fields), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Validator) AvailableFields() []string {
|
func (p *Validator) AvailableFields() []string {
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
// +build !consulent
|
||||||
|
|
||||||
|
package kubeauth
|
||||||
|
|
||||||
|
import "github.com/hashicorp/consul/agent/structs"
|
||||||
|
|
||||||
|
type enterpriseConfig struct{}
|
||||||
|
|
||||||
|
func (v *Validator) k8sEntMetaFromFields(fields map[string]string) *structs.EnterpriseMeta {
|
||||||
|
return structs.DefaultEnterpriseMeta()
|
||||||
|
}
|
|
@ -35,12 +35,12 @@ func TestValidateLogin(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Run("invalid bearer token", func(t *testing.T) {
|
t.Run("invalid bearer token", func(t *testing.T) {
|
||||||
_, err := validator.ValidateLogin("invalid")
|
_, _, err := validator.ValidateLogin("invalid")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("valid bearer token", func(t *testing.T) {
|
t.Run("valid bearer token", func(t *testing.T) {
|
||||||
fields, err := validator.ValidateLogin(goodJWT_B)
|
fields, _, err := validator.ValidateLogin(goodJWT_B)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, map[string]string{
|
require.Equal(t, map[string]string{
|
||||||
"serviceaccount.namespace": "default",
|
"serviceaccount.namespace": "default",
|
||||||
|
@ -59,7 +59,7 @@ func TestValidateLogin(t *testing.T) {
|
||||||
)
|
)
|
||||||
|
|
||||||
t.Run("valid bearer token with annotation", func(t *testing.T) {
|
t.Run("valid bearer token with annotation", func(t *testing.T) {
|
||||||
fields, err := validator.ValidateLogin(goodJWT_B)
|
fields, _, err := validator.ValidateLogin(goodJWT_B)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, map[string]string{
|
require.Equal(t, map[string]string{
|
||||||
"serviceaccount.namespace": "default",
|
"serviceaccount.namespace": "default",
|
||||||
|
|
|
@ -80,6 +80,8 @@ func GetSessionToken(sessionID string, token string) (map[string]string, bool) {
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
SessionID string // unique identifier for this set of tokens in the database
|
SessionID string // unique identifier for this set of tokens in the database
|
||||||
|
|
||||||
|
enterpriseConfig `mapstructure:",squash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newValidator(method *structs.ACLAuthMethod) (authmethod.Validator, error) {
|
func newValidator(method *structs.ACLAuthMethod) (authmethod.Validator, error) {
|
||||||
|
@ -120,13 +122,13 @@ func (v *Validator) Name() string { return v.name }
|
||||||
// to extend the life of the underlying token.
|
// to extend the life of the underlying token.
|
||||||
//
|
//
|
||||||
// Returns auth method specific metadata suitable for the Role Binding process.
|
// Returns auth method specific metadata suitable for the Role Binding process.
|
||||||
func (v *Validator) ValidateLogin(loginToken string) (map[string]string, error) {
|
func (v *Validator) ValidateLogin(loginToken string) (map[string]string, *structs.EnterpriseMeta, error) {
|
||||||
fields, valid := GetSessionToken(v.config.SessionID, loginToken)
|
fields, valid := GetSessionToken(v.config.SessionID, loginToken)
|
||||||
if !valid {
|
if !valid {
|
||||||
return nil, acl.ErrNotFound
|
return nil, nil, acl.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return fields, nil
|
return fields, v.testAuthEntMetaFromFields(fields), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) AvailableFields() []string { return availableFields }
|
func (v *Validator) AvailableFields() []string { return availableFields }
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
// +build !consulent
|
||||||
|
|
||||||
|
package testauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type enterpriseConfig struct{}
|
||||||
|
|
||||||
|
func (v *Validator) testAuthEntMetaFromFields(fields map[string]string) *structs.EnterpriseMeta {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -728,7 +728,7 @@ func (s *Store) aclTokenSetTxn(tx *memdb.Txn, idx uint64, token *structs.ACLToke
|
||||||
}
|
}
|
||||||
|
|
||||||
if token.AuthMethod != "" {
|
if token.AuthMethod != "" {
|
||||||
method, err := s.getAuthMethodWithTxn(tx, nil, token.AuthMethod, &token.EnterpriseMeta)
|
method, err := s.getAuthMethodWithTxn(tx, nil, token.AuthMethod, token.ACLAuthMethodEnterpriseMeta.ToEnterpriseMeta())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if method == nil {
|
} else if method == nil {
|
||||||
|
@ -838,7 +838,7 @@ func (s *Store) aclTokenGetTxn(tx *memdb.Txn, ws memdb.WatchSet, value, index st
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACLTokenList is used to list out all of the ACLs in the state store.
|
// ACLTokenList is used to list out all of the ACLs in the state store.
|
||||||
func (s *Store) ACLTokenList(ws memdb.WatchSet, local, global bool, policy, role, methodName string, entMeta *structs.EnterpriseMeta) (uint64, structs.ACLTokens, error) {
|
func (s *Store) ACLTokenList(ws memdb.WatchSet, local, global bool, policy, role, methodName string, methodMeta, entMeta *structs.EnterpriseMeta) (uint64, structs.ACLTokens, error) {
|
||||||
tx := s.db.Txn(false)
|
tx := s.db.Txn(false)
|
||||||
defer tx.Abort()
|
defer tx.Abort()
|
||||||
|
|
||||||
|
@ -868,7 +868,7 @@ func (s *Store) ACLTokenList(ws memdb.WatchSet, local, global bool, policy, role
|
||||||
needLocalityFilter = true
|
needLocalityFilter = true
|
||||||
|
|
||||||
} else if policy == "" && role == "" && methodName != "" {
|
} else if policy == "" && role == "" && methodName != "" {
|
||||||
iter, err = s.aclTokenListByAuthMethod(tx, methodName, entMeta)
|
iter, err = s.aclTokenListByAuthMethod(tx, methodName, methodMeta, entMeta)
|
||||||
needLocalityFilter = true
|
needLocalityFilter = true
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -1052,9 +1052,9 @@ func (s *Store) aclTokenDeleteTxn(tx *memdb.Txn, idx uint64, value, index string
|
||||||
return s.aclTokenDeleteWithToken(tx, token.(*structs.ACLToken), idx)
|
return s.aclTokenDeleteWithToken(tx, token.(*structs.ACLToken), idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) aclTokenDeleteAllForAuthMethodTxn(tx *memdb.Txn, idx uint64, methodName string, entMeta *structs.EnterpriseMeta) error {
|
func (s *Store) aclTokenDeleteAllForAuthMethodTxn(tx *memdb.Txn, idx uint64, methodName string, methodMeta *structs.EnterpriseMeta) error {
|
||||||
// collect them all
|
// collect all the tokens linked with the given auth method.
|
||||||
iter, err := s.aclTokenListByAuthMethod(tx, methodName, entMeta)
|
iter, err := s.aclTokenListByAuthMethod(tx, methodName, methodMeta, structs.WildcardEnterpriseMeta())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed acl token lookup: %v", err)
|
return fmt.Errorf("failed acl token lookup: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -297,7 +297,7 @@ func (s *Store) aclTokenListByRole(tx *memdb.Txn, role string, _ *structs.Enterp
|
||||||
return tx.Get("acl-tokens", "roles", role)
|
return tx.Get("acl-tokens", "roles", role)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) aclTokenListByAuthMethod(tx *memdb.Txn, authMethod string, _ *structs.EnterpriseMeta) (memdb.ResultIterator, error) {
|
func (s *Store) aclTokenListByAuthMethod(tx *memdb.Txn, authMethod string, _, _ *structs.EnterpriseMeta) (memdb.ResultIterator, error) {
|
||||||
return tx.Get("acl-tokens", "authmethod", authMethod)
|
return tx.Get("acl-tokens", "authmethod", authMethod)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -218,7 +218,7 @@ func TestStateStore_ACLBootstrap(t *testing.T) {
|
||||||
require.Equal(t, uint64(3), index)
|
require.Equal(t, uint64(3), index)
|
||||||
|
|
||||||
// Make sure the ACLs are in an expected state.
|
// Make sure the ACLs are in an expected state.
|
||||||
_, tokens, err := s.ACLTokenList(nil, true, true, "", "", "", nil)
|
_, tokens, err := s.ACLTokenList(nil, true, true, "", "", "", nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, tokens, 1)
|
require.Len(t, tokens, 1)
|
||||||
compareTokens(t, token1, tokens[0])
|
compareTokens(t, token1, tokens[0])
|
||||||
|
@ -232,7 +232,7 @@ func TestStateStore_ACLBootstrap(t *testing.T) {
|
||||||
err = s.ACLBootstrap(32, index, token2.Clone(), false)
|
err = s.ACLBootstrap(32, index, token2.Clone(), false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, tokens, err = s.ACLTokenList(nil, true, true, "", "", "", nil)
|
_, tokens, err = s.ACLTokenList(nil, true, true, "", "", "", nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, tokens, 2)
|
require.Len(t, tokens, 2)
|
||||||
}
|
}
|
||||||
|
@ -1203,7 +1203,7 @@ func TestStateStore_ACLToken_List(t *testing.T) {
|
||||||
{testPolicyID_A, testRoleID_A, ""},
|
{testPolicyID_A, testRoleID_A, ""},
|
||||||
} {
|
} {
|
||||||
t.Run(fmt.Sprintf("can't filter on more than one: %s/%s/%s", tc.policy, tc.role, tc.methodName), func(t *testing.T) {
|
t.Run(fmt.Sprintf("can't filter on more than one: %s/%s/%s", tc.policy, tc.role, tc.methodName), func(t *testing.T) {
|
||||||
_, _, err := s.ACLTokenList(nil, false, false, tc.policy, tc.role, tc.methodName, nil)
|
_, _, err := s.ACLTokenList(nil, false, false, tc.policy, tc.role, tc.methodName, nil, nil)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1212,7 +1212,7 @@ func TestStateStore_ACLToken_List(t *testing.T) {
|
||||||
tc := tc // capture range variable
|
tc := tc // capture range variable
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
_, tokens, err := s.ACLTokenList(nil, tc.local, tc.global, tc.policy, tc.role, tc.methodName, nil)
|
_, tokens, err := s.ACLTokenList(nil, tc.local, tc.global, tc.policy, tc.role, tc.methodName, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, tokens, len(tc.accessors))
|
require.Len(t, tokens, len(tc.accessors))
|
||||||
tokens.Sort()
|
tokens.Sort()
|
||||||
|
@ -1273,7 +1273,7 @@ func TestStateStore_ACLToken_FixupPolicyLinks(t *testing.T) {
|
||||||
require.Equal(t, "node-read-renamed", retrieved.Policies[0].Name)
|
require.Equal(t, "node-read-renamed", retrieved.Policies[0].Name)
|
||||||
|
|
||||||
// list tokens without stale links
|
// list tokens without stale links
|
||||||
_, tokens, err := s.ACLTokenList(nil, true, true, "", "", "", nil)
|
_, tokens, err := s.ACLTokenList(nil, true, true, "", "", "", nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
|
@ -1317,7 +1317,7 @@ func TestStateStore_ACLToken_FixupPolicyLinks(t *testing.T) {
|
||||||
require.Len(t, retrieved.Policies, 0)
|
require.Len(t, retrieved.Policies, 0)
|
||||||
|
|
||||||
// list tokens without stale links
|
// list tokens without stale links
|
||||||
_, tokens, err = s.ACLTokenList(nil, true, true, "", "", "", nil)
|
_, tokens, err = s.ACLTokenList(nil, true, true, "", "", "", nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
found = false
|
found = false
|
||||||
|
@ -1402,7 +1402,7 @@ func TestStateStore_ACLToken_FixupRoleLinks(t *testing.T) {
|
||||||
require.Equal(t, "node-read-role-renamed", retrieved.Roles[0].Name)
|
require.Equal(t, "node-read-role-renamed", retrieved.Roles[0].Name)
|
||||||
|
|
||||||
// list tokens without stale links
|
// list tokens without stale links
|
||||||
_, tokens, err := s.ACLTokenList(nil, true, true, "", "", "", nil)
|
_, tokens, err := s.ACLTokenList(nil, true, true, "", "", "", nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
|
@ -1446,7 +1446,7 @@ func TestStateStore_ACLToken_FixupRoleLinks(t *testing.T) {
|
||||||
require.Len(t, retrieved.Roles, 0)
|
require.Len(t, retrieved.Roles, 0)
|
||||||
|
|
||||||
// list tokens without stale links
|
// list tokens without stale links
|
||||||
_, tokens, err = s.ACLTokenList(nil, true, true, "", "", "", nil)
|
_, tokens, err = s.ACLTokenList(nil, true, true, "", "", "", nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
found = false
|
found = false
|
||||||
|
@ -3610,7 +3610,7 @@ func TestStateStore_ACLTokens_Snapshot_Restore(t *testing.T) {
|
||||||
require.NoError(t, s.ACLRoleBatchSet(2, roles, false))
|
require.NoError(t, s.ACLRoleBatchSet(2, roles, false))
|
||||||
|
|
||||||
// Read the restored ACLs back out and verify that they match.
|
// Read the restored ACLs back out and verify that they match.
|
||||||
idx, res, err := s.ACLTokenList(nil, true, true, "", "", "", nil)
|
idx, res, err := s.ACLTokenList(nil, true, true, "", "", "", nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, uint64(4), idx)
|
require.Equal(t, uint64(4), idx)
|
||||||
require.ElementsMatch(t, tokens, res)
|
require.ElementsMatch(t, tokens, res)
|
||||||
|
|
|
@ -44,3 +44,11 @@ func (s *HTTPServer) rewordUnknownEnterpriseFieldError(err error) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPServer) addEnterpriseHTMLTemplateVars(vars map[string]interface{}) {}
|
func (s *HTTPServer) addEnterpriseHTMLTemplateVars(vars map[string]interface{}) {}
|
||||||
|
|
||||||
|
func parseACLAuthMethodEnterpriseMeta(req *http.Request, _ *structs.ACLAuthMethodEnterpriseMeta) error {
|
||||||
|
if methodNS := req.URL.Query().Get("authmethod-ns"); methodNS != "" {
|
||||||
|
return BadRequestError{Reason: "Invalid query paramter: \"authmethod-ns\" - Namespaces is a Consul Enterprise feature"}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/lib"
|
"github.com/hashicorp/consul/lib"
|
||||||
|
"github.com/hashicorp/go-msgpack/codec"
|
||||||
"golang.org/x/crypto/blake2b"
|
"golang.org/x/crypto/blake2b"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -227,6 +228,9 @@ type ACLToken struct {
|
||||||
// AuthMethod is the name of the auth method used to create this token.
|
// AuthMethod is the name of the auth method used to create this token.
|
||||||
AuthMethod string `json:",omitempty"`
|
AuthMethod string `json:",omitempty"`
|
||||||
|
|
||||||
|
// ACLAuthMethodEnterpriseMeta is the EnterpriseMeta for the AuthMethod that this token was created from
|
||||||
|
ACLAuthMethodEnterpriseMeta
|
||||||
|
|
||||||
// ExpirationTime represents the point after which a token should be
|
// ExpirationTime represents the point after which a token should be
|
||||||
// considered revoked and is eligible for destruction. The zero value
|
// considered revoked and is eligible for destruction. The zero value
|
||||||
// represents NO expiration.
|
// represents NO expiration.
|
||||||
|
@ -1044,6 +1048,49 @@ type ACLAuthMethod struct {
|
||||||
RaftIndex `hash:"ignore"`
|
RaftIndex `hash:"ignore"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalBinary writes ACLAuthMethod as msgpack encoded. It's only here
|
||||||
|
// because we need custom decoding of the raw interface{} values and this
|
||||||
|
// completes the interface.
|
||||||
|
func (m *ACLAuthMethod) MarshalBinary() (data []byte, err error) {
|
||||||
|
// bs will grow if needed but allocate enough to avoid reallocation in common
|
||||||
|
// case.
|
||||||
|
bs := make([]byte, 256)
|
||||||
|
enc := codec.NewEncoderBytes(&bs, msgpackHandle)
|
||||||
|
|
||||||
|
type Alias ACLAuthMethod
|
||||||
|
|
||||||
|
if err := enc.Encode((*Alias)(m)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary decodes msgpack encoded ACLAuthMethod. It used
|
||||||
|
// default msgpack encoding but fixes up the uint8 strings and other problems we
|
||||||
|
// have with encoding map[string]interface{}.
|
||||||
|
func (m *ACLAuthMethod) UnmarshalBinary(data []byte) error {
|
||||||
|
dec := codec.NewDecoderBytes(data, msgpackHandle)
|
||||||
|
|
||||||
|
type Alias ACLAuthMethod
|
||||||
|
var a Alias
|
||||||
|
|
||||||
|
if err := dec.Decode(&a); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*m = ACLAuthMethod(a)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Fix strings and maps in the returned maps
|
||||||
|
m.Config, err = lib.MapWalk(m.Config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type ACLReplicationType string
|
type ACLReplicationType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -1128,6 +1175,7 @@ type ACLTokenListRequest struct {
|
||||||
Role string // Role filter
|
Role string // Role filter
|
||||||
AuthMethod string // Auth Method filter
|
AuthMethod string // Auth Method filter
|
||||||
Datacenter string // The datacenter to perform the request within
|
Datacenter string // The datacenter to perform the request within
|
||||||
|
ACLAuthMethodEnterpriseMeta
|
||||||
EnterpriseMeta
|
EnterpriseMeta
|
||||||
QueryOptions
|
QueryOptions
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,16 @@ node_prefix "" {
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ACLAuthMethodEnterpriseMeta struct{}
|
||||||
|
|
||||||
|
func (_ *ACLAuthMethodEnterpriseMeta) FillWithEnterpriseMeta(_ *EnterpriseMeta) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *ACLAuthMethodEnterpriseMeta) ToEnterpriseMeta() *EnterpriseMeta {
|
||||||
|
return DefaultEnterpriseMeta()
|
||||||
|
}
|
||||||
|
|
||||||
func aclServiceIdentityRules(svc string, _ *EnterpriseMeta) string {
|
func aclServiceIdentityRules(svc string, _ *EnterpriseMeta) string {
|
||||||
return fmt.Sprintf(aclPolicyTemplateServiceIdentity, svc)
|
return fmt.Sprintf(aclPolicyTemplateServiceIdentity, svc)
|
||||||
}
|
}
|
||||||
|
@ -35,3 +45,7 @@ func aclServiceIdentityRules(svc string, _ *EnterpriseMeta) string {
|
||||||
func (p *ACLPolicy) EnterprisePolicyMeta() *acl.EnterprisePolicyMeta {
|
func (p *ACLPolicy) EnterprisePolicyMeta() *acl.EnterprisePolicyMeta {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *ACLAuthMethod) TargetEnterpriseMeta(_ *EnterpriseMeta) *EnterpriseMeta {
|
||||||
|
return &m.EnterpriseMeta
|
||||||
|
}
|
||||||
|
|
|
@ -553,6 +553,12 @@ The table below shows this endpoint's support for
|
||||||
- `authmethod` `(string: "")` - Filters the token list to those tokens that are
|
- `authmethod` `(string: "")` - Filters the token list to those tokens that are
|
||||||
linked with the specific named auth method.
|
linked with the specific named auth method.
|
||||||
|
|
||||||
|
- `authmethod-ns` `(string: "")` - **(Enterprise Only)** Specifics the namespace
|
||||||
|
of the `authmethod` being used for token lookup. If not provided, the namespace
|
||||||
|
provided by the `ns` parameter will be used. If neither of those is provided
|
||||||
|
then the namespace will be inherited from the request's ACL token. Added in
|
||||||
|
Consul 1.7.0.
|
||||||
|
|
||||||
- `ns` `(string: "")` - **(Enterprise Only)** Specifies the namespace to list
|
- `ns` `(string: "")` - **(Enterprise Only)** Specifies the namespace to list
|
||||||
the tokens for. This value can be specified as the `ns` URL query
|
the tokens for. This value can be specified as the `ns` URL query
|
||||||
parameter or the `X-Consul-Namespace` header. If not provided by either,
|
parameter or the `X-Consul-Namespace` header. If not provided by either,
|
||||||
|
|
|
@ -35,6 +35,23 @@ parameters are required to properly configure an auth method of type
|
||||||
([JWT](https://jwt.io/ "JSON Web Token")) used by the Consul leader to
|
([JWT](https://jwt.io/ "JSON Web Token")) used by the Consul leader to
|
||||||
validate application JWTs during login.
|
validate application JWTs during login.
|
||||||
|
|
||||||
|
- `MapNamespaces` `(bool: <false>)` - **(Enterprise Only)** Indicates whether
|
||||||
|
the auth method should attempt to map the Kubernetes namespace to a Consul
|
||||||
|
namespace instead of creating tokens in the auth methods own namespace. Note
|
||||||
|
that mapping namespaces requires the auth method to reside within the
|
||||||
|
`default` namespace.
|
||||||
|
|
||||||
|
- `ConsulNamespacePrefix` `(string: <optional>)` - **(Enterprise Only)** When
|
||||||
|
`MapNamespaces` is enabled, this value will be prefixed to the Kubernetes
|
||||||
|
namespace to determine the Consul namespace to create the new token within.
|
||||||
|
|
||||||
|
- `ConsulNamespaceOverrides` `(map: <string:string>)` - **(Enterprise Only)**
|
||||||
|
This field is a mapping of Kubernetes namespace names to Consul namespace
|
||||||
|
names. If a Kubernetes namespace is present within this map, the value will
|
||||||
|
be used without adding the `ConsulNamespacePrefix`. If the value in the map
|
||||||
|
is `""` then the auth methods namespace will be used instead of attempting
|
||||||
|
to determine an alternate namespace.
|
||||||
|
|
||||||
### Sample Config
|
### Sample Config
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|
Loading…
Reference in New Issue