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.Role = req.URL.Query().Get("role")
|
||||
args.AuthMethod = req.URL.Query().Get("authmethod")
|
||||
if err := parseACLAuthMethodEnterpriseMeta(req, &args.ACLAuthMethodEnterpriseMeta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var out structs.ACLTokenListResponse
|
||||
defer setMeta(resp, &out.QueryMeta)
|
||||
|
|
|
@ -1405,7 +1405,6 @@ func (s *HTTPServer) AgentHost(resp http.ResponseWriter, req *http.Request) (int
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// TODO (namespaces) - pass through a real ent authz ctx
|
||||
if rule != nil && rule.OperatorRead(nil) != acl.Allow {
|
||||
return nil, acl.ErrPermissionDenied
|
||||
}
|
||||
|
|
|
@ -43,10 +43,11 @@ func (s *Server) loadAuthMethodValidator(idx uint64, method *structs.ACLAuthMeth
|
|||
func (s *Server) evaluateRoleBindings(
|
||||
validator authmethod.Validator,
|
||||
verifiedFields map[string]string,
|
||||
entMeta *structs.EnterpriseMeta,
|
||||
methodMeta *structs.EnterpriseMeta,
|
||||
targetMeta *structs.EnterpriseMeta,
|
||||
) ([]*structs.ACLServiceIdentity, []structs.ACLTokenRoleLink, error) {
|
||||
// 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 {
|
||||
return nil, nil, err
|
||||
} else if len(rules) == 0 {
|
||||
|
@ -87,7 +88,7 @@ func (s *Server) evaluateRoleBindings(
|
|||
})
|
||||
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
@ -828,9 +828,15 @@ func (a *ACL) TokenList(args *structs.ACLTokenListRequest, reply *structs.ACLTok
|
|||
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,
|
||||
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 {
|
||||
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
|
||||
verifiedFields, err := validator.ValidateLogin(auth.BearerToken)
|
||||
verifiedFields, desiredMeta, err := validator.ValidateLogin(auth.BearerToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// This always will return a valid pointer
|
||||
targetMeta := method.TargetEnterpriseMeta(desiredMeta)
|
||||
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
|
@ -2256,11 +2265,13 @@ func (a *ACL) Login(args *structs.ACLLoginRequest, reply *structs.ACLToken) erro
|
|||
AuthMethod: auth.AuthMethod,
|
||||
ServiceIdentities: serviceIdentities,
|
||||
Roles: roleLinks,
|
||||
EnterpriseMeta: auth.EnterpriseMeta,
|
||||
EnterpriseMeta: *targetMeta,
|
||||
},
|
||||
WriteRequest: args.WriteRequest,
|
||||
}
|
||||
|
||||
createReq.ACLToken.ACLAuthMethodEnterpriseMeta.FillWithEnterpriseMeta(&auth.EnterpriseMeta)
|
||||
|
||||
// 5. return token information like a TokenCreate would
|
||||
err = a.tokenSetInternal(&createReq, reply, true)
|
||||
|
||||
|
|
|
@ -262,7 +262,7 @@ func (a *ACL) List(args *structs.DCSpecificRequest,
|
|||
return a.srv.blockingQuery(&args.QueryOptions,
|
||||
&reply.QueryMeta,
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -138,7 +138,7 @@ func reconcileLegacyACLs(local, remote structs.ACLs, lastRemoteIndex uint64) str
|
|||
|
||||
// FetchLocalACLs returns the ACLs in the local state store.
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -396,11 +396,11 @@ func TestACLReplication_LegacyTokens(t *testing.T) {
|
|||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -351,9 +351,9 @@ func TestACLReplication_Tokens(t *testing.T) {
|
|||
|
||||
checkSame := func(t *retry.R) {
|
||||
// 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)
|
||||
_, 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.Len(t, local, len(remote))
|
||||
|
@ -451,7 +451,7 @@ func TestACLReplication_Tokens(t *testing.T) {
|
|||
})
|
||||
|
||||
// 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.Len(t, local, 50)
|
||||
|
||||
|
@ -787,10 +787,10 @@ func TestACLReplication_AllTypes(t *testing.T) {
|
|||
|
||||
checkSameTokens := func(t *retry.R) {
|
||||
// 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)
|
||||
// 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.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) {
|
||||
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 {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
|
|
@ -39,8 +39,9 @@ type Validator interface {
|
|||
// continue to extend the life of the underlying token.
|
||||
//
|
||||
// Returns auth method specific metadata suitable for the Role Binding
|
||||
// process.
|
||||
ValidateLogin(loginToken string) (map[string]string, error)
|
||||
// process as well as the desired enterprise meta for the token to be
|
||||
// created.
|
||||
ValidateLogin(loginToken string) (map[string]string, *structs.EnterpriseMeta, error)
|
||||
|
||||
// AvailableFields returns a slice of all fields that are returned as a
|
||||
// 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
|
||||
// annotations.
|
||||
ServiceAccountJWT string `json:",omitempty"`
|
||||
|
||||
enterpriseConfig `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
// 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) 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 {
|
||||
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.
|
||||
|
@ -129,24 +131,24 @@ func (v *Validator) ValidateLogin(loginToken string) (map[string]string, error)
|
|||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
} 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 {
|
||||
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)
|
||||
parts := strings.Split(trResp.Status.User.Username, ":")
|
||||
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
|
||||
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 (
|
||||
|
@ -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.
|
||||
sa, err := v.saGetter.ServiceAccounts(saNamespace).Get(saName, client_metav1.GetOptions{})
|
||||
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()
|
||||
|
@ -166,11 +168,13 @@ func (v *Validator) ValidateLogin(loginToken string) (map[string]string, error)
|
|||
saName = serviceNameOverride
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
fields := map[string]string{
|
||||
serviceAccountNamespaceField: saNamespace,
|
||||
serviceAccountNameField: saName,
|
||||
serviceAccountUIDField: saUID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return fields, v.k8sEntMetaFromFields(fields), nil
|
||||
}
|
||||
|
||||
func (p *Validator) AvailableFields() []string {
|
||||
|
|
11
agent/consul/authmethod/kubeauth/k8s_oss.go
Normal file
11
agent/consul/authmethod/kubeauth/k8s_oss.go
Normal file
|
@ -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)
|
||||
|
||||
t.Run("invalid bearer token", func(t *testing.T) {
|
||||
_, err := validator.ValidateLogin("invalid")
|
||||
_, _, err := validator.ValidateLogin("invalid")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
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.Equal(t, map[string]string{
|
||||
"serviceaccount.namespace": "default",
|
||||
|
@ -59,7 +59,7 @@ func TestValidateLogin(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.Equal(t, map[string]string{
|
||||
"serviceaccount.namespace": "default",
|
||||
|
|
|
@ -80,6 +80,8 @@ func GetSessionToken(sessionID string, token string) (map[string]string, bool) {
|
|||
|
||||
type Config struct {
|
||||
SessionID string // unique identifier for this set of tokens in the database
|
||||
|
||||
enterpriseConfig `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
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.
|
||||
//
|
||||
// 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)
|
||||
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 }
|
||||
|
|
13
agent/consul/authmethod/testauth/testing_oss.go
Normal file
13
agent/consul/authmethod/testauth/testing_oss.go
Normal file
|
@ -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 != "" {
|
||||
method, err := s.getAuthMethodWithTxn(tx, nil, token.AuthMethod, &token.EnterpriseMeta)
|
||||
method, err := s.getAuthMethodWithTxn(tx, nil, token.AuthMethod, token.ACLAuthMethodEnterpriseMeta.ToEnterpriseMeta())
|
||||
if err != nil {
|
||||
return err
|
||||
} 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.
|
||||
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)
|
||||
defer tx.Abort()
|
||||
|
||||
|
@ -868,7 +868,7 @@ func (s *Store) ACLTokenList(ws memdb.WatchSet, local, global bool, policy, role
|
|||
needLocalityFilter = true
|
||||
|
||||
} else if policy == "" && role == "" && methodName != "" {
|
||||
iter, err = s.aclTokenListByAuthMethod(tx, methodName, entMeta)
|
||||
iter, err = s.aclTokenListByAuthMethod(tx, methodName, methodMeta, entMeta)
|
||||
needLocalityFilter = true
|
||||
|
||||
} 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)
|
||||
}
|
||||
|
||||
func (s *Store) aclTokenDeleteAllForAuthMethodTxn(tx *memdb.Txn, idx uint64, methodName string, entMeta *structs.EnterpriseMeta) error {
|
||||
// collect them all
|
||||
iter, err := s.aclTokenListByAuthMethod(tx, methodName, entMeta)
|
||||
func (s *Store) aclTokenDeleteAllForAuthMethodTxn(tx *memdb.Txn, idx uint64, methodName string, methodMeta *structs.EnterpriseMeta) error {
|
||||
// collect all the tokens linked with the given auth method.
|
||||
iter, err := s.aclTokenListByAuthMethod(tx, methodName, methodMeta, structs.WildcardEnterpriseMeta())
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -218,7 +218,7 @@ func TestStateStore_ACLBootstrap(t *testing.T) {
|
|||
require.Equal(t, uint64(3), index)
|
||||
|
||||
// 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.Len(t, tokens, 1)
|
||||
compareTokens(t, token1, tokens[0])
|
||||
|
@ -232,7 +232,7 @@ func TestStateStore_ACLBootstrap(t *testing.T) {
|
|||
err = s.ACLBootstrap(32, index, token2.Clone(), false)
|
||||
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.Len(t, tokens, 2)
|
||||
}
|
||||
|
@ -1203,7 +1203,7 @@ func TestStateStore_ACLToken_List(t *testing.T) {
|
|||
{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) {
|
||||
_, _, 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)
|
||||
})
|
||||
}
|
||||
|
@ -1212,7 +1212,7 @@ func TestStateStore_ACLToken_List(t *testing.T) {
|
|||
tc := tc // capture range variable
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
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.Len(t, tokens, len(tc.accessors))
|
||||
tokens.Sort()
|
||||
|
@ -1273,7 +1273,7 @@ func TestStateStore_ACLToken_FixupPolicyLinks(t *testing.T) {
|
|||
require.Equal(t, "node-read-renamed", retrieved.Policies[0].Name)
|
||||
|
||||
// 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)
|
||||
|
||||
found := false
|
||||
|
@ -1317,7 +1317,7 @@ func TestStateStore_ACLToken_FixupPolicyLinks(t *testing.T) {
|
|||
require.Len(t, retrieved.Policies, 0)
|
||||
|
||||
// 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)
|
||||
|
||||
found = false
|
||||
|
@ -1402,7 +1402,7 @@ func TestStateStore_ACLToken_FixupRoleLinks(t *testing.T) {
|
|||
require.Equal(t, "node-read-role-renamed", retrieved.Roles[0].Name)
|
||||
|
||||
// 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)
|
||||
|
||||
found := false
|
||||
|
@ -1446,7 +1446,7 @@ func TestStateStore_ACLToken_FixupRoleLinks(t *testing.T) {
|
|||
require.Len(t, retrieved.Roles, 0)
|
||||
|
||||
// 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)
|
||||
|
||||
found = false
|
||||
|
@ -3610,7 +3610,7 @@ func TestStateStore_ACLTokens_Snapshot_Restore(t *testing.T) {
|
|||
require.NoError(t, s.ACLRoleBatchSet(2, roles, false))
|
||||
|
||||
// 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.Equal(t, uint64(4), idx)
|
||||
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 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/lib"
|
||||
"github.com/hashicorp/go-msgpack/codec"
|
||||
"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 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
|
||||
// considered revoked and is eligible for destruction. The zero value
|
||||
// represents NO expiration.
|
||||
|
@ -1044,6 +1048,49 @@ type ACLAuthMethod struct {
|
|||
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
|
||||
|
||||
const (
|
||||
|
@ -1128,6 +1175,7 @@ type ACLTokenListRequest struct {
|
|||
Role string // Role filter
|
||||
AuthMethod string // Auth Method filter
|
||||
Datacenter string // The datacenter to perform the request within
|
||||
ACLAuthMethodEnterpriseMeta
|
||||
EnterpriseMeta
|
||||
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 {
|
||||
return fmt.Sprintf(aclPolicyTemplateServiceIdentity, svc)
|
||||
}
|
||||
|
@ -35,3 +45,7 @@ func aclServiceIdentityRules(svc string, _ *EnterpriseMeta) string {
|
|||
func (p *ACLPolicy) EnterprisePolicyMeta() *acl.EnterprisePolicyMeta {
|
||||
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
|
||||
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
|
||||
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,
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
||||
```json
|
||||
|
|
Loading…
Reference in a new issue