diff --git a/agent/acl_endpoint.go b/agent/acl_endpoint.go index a3ab26c24..ac882acbb 100644 --- a/agent/acl_endpoint.go +++ b/agent/acl_endpoint.go @@ -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) diff --git a/agent/agent_endpoint.go b/agent/agent_endpoint.go index 0b7b6034a..a55f90254 100644 --- a/agent/agent_endpoint.go +++ b/agent/agent_endpoint.go @@ -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 } diff --git a/agent/consul/acl_authmethod.go b/agent/consul/acl_authmethod.go index 7d709f4ba..a376af53c 100644 --- a/agent/consul/acl_authmethod.go +++ b/agent/consul/acl_authmethod.go @@ -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 } diff --git a/agent/consul/acl_endpoint.go b/agent/consul/acl_endpoint.go index 4b7c35168..051253603 100644 --- a/agent/consul/acl_endpoint.go +++ b/agent/consul/acl_endpoint.go @@ -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) diff --git a/agent/consul/acl_endpoint_legacy.go b/agent/consul/acl_endpoint_legacy.go index 6a01882c4..1a2fea047 100644 --- a/agent/consul/acl_endpoint_legacy.go +++ b/agent/consul/acl_endpoint_legacy.go @@ -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 } diff --git a/agent/consul/acl_replication_legacy.go b/agent/consul/acl_replication_legacy.go index a14e416f2..9931c1b51 100644 --- a/agent/consul/acl_replication_legacy.go +++ b/agent/consul/acl_replication_legacy.go @@ -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 } diff --git a/agent/consul/acl_replication_legacy_test.go b/agent/consul/acl_replication_legacy_test.go index 9149122ae..ca7770375 100644 --- a/agent/consul/acl_replication_legacy_test.go +++ b/agent/consul/acl_replication_legacy_test.go @@ -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 } diff --git a/agent/consul/acl_replication_test.go b/agent/consul/acl_replication_test.go index 6ff55b8f8..3cafca58a 100644 --- a/agent/consul/acl_replication_test.go +++ b/agent/consul/acl_replication_test.go @@ -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)) diff --git a/agent/consul/acl_replication_types.go b/agent/consul/acl_replication_types.go index 4cd4dcefa..a238244cf 100644 --- a/agent/consul/acl_replication_types.go +++ b/agent/consul/acl_replication_types.go @@ -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 } diff --git a/agent/consul/authmethod/authmethods.go b/agent/consul/authmethod/authmethods.go index 65a4e07fa..013d75ac9 100644 --- a/agent/consul/authmethod/authmethods.go +++ b/agent/consul/authmethod/authmethods.go @@ -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 diff --git a/agent/consul/authmethod/kubeauth/k8s.go b/agent/consul/authmethod/kubeauth/k8s.go index 88c4b32e3..99d95a8df 100644 --- a/agent/consul/authmethod/kubeauth/k8s.go +++ b/agent/consul/authmethod/kubeauth/k8s.go @@ -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 { diff --git a/agent/consul/authmethod/kubeauth/k8s_oss.go b/agent/consul/authmethod/kubeauth/k8s_oss.go new file mode 100644 index 000000000..5904dd68a --- /dev/null +++ b/agent/consul/authmethod/kubeauth/k8s_oss.go @@ -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() +} diff --git a/agent/consul/authmethod/kubeauth/k8s_test.go b/agent/consul/authmethod/kubeauth/k8s_test.go index 614538c40..20771e275 100644 --- a/agent/consul/authmethod/kubeauth/k8s_test.go +++ b/agent/consul/authmethod/kubeauth/k8s_test.go @@ -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", diff --git a/agent/consul/authmethod/testauth/testing.go b/agent/consul/authmethod/testauth/testing.go index 638450d94..f76053ec4 100644 --- a/agent/consul/authmethod/testauth/testing.go +++ b/agent/consul/authmethod/testauth/testing.go @@ -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 } diff --git a/agent/consul/authmethod/testauth/testing_oss.go b/agent/consul/authmethod/testauth/testing_oss.go new file mode 100644 index 000000000..5ae0c8124 --- /dev/null +++ b/agent/consul/authmethod/testauth/testing_oss.go @@ -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 +} diff --git a/agent/consul/state/acl.go b/agent/consul/state/acl.go index fdef4d763..b41a0c852 100644 --- a/agent/consul/state/acl.go +++ b/agent/consul/state/acl.go @@ -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) } diff --git a/agent/consul/state/acl_oss.go b/agent/consul/state/acl_oss.go index bbf9ed6a8..2970ea77f 100644 --- a/agent/consul/state/acl_oss.go +++ b/agent/consul/state/acl_oss.go @@ -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) } diff --git a/agent/consul/state/acl_test.go b/agent/consul/state/acl_test.go index 62b2c2859..73d8c8c3e 100644 --- a/agent/consul/state/acl_test.go +++ b/agent/consul/state/acl_test.go @@ -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) diff --git a/agent/http_oss.go b/agent/http_oss.go index 835516c8f..4e753130f 100644 --- a/agent/http_oss.go +++ b/agent/http_oss.go @@ -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 +} diff --git a/agent/structs/acl.go b/agent/structs/acl.go index 8e2cff458..6ce8ac2f3 100644 --- a/agent/structs/acl.go +++ b/agent/structs/acl.go @@ -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 } diff --git a/agent/structs/acl_oss.go b/agent/structs/acl_oss.go index 30173e97b..ca30f5775 100644 --- a/agent/structs/acl_oss.go +++ b/agent/structs/acl_oss.go @@ -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 +} diff --git a/website/source/api/acl/tokens.html.md b/website/source/api/acl/tokens.html.md index a79ae70d2..45bc421ca 100644 --- a/website/source/api/acl/tokens.html.md +++ b/website/source/api/acl/tokens.html.md @@ -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, diff --git a/website/source/docs/acl/auth-methods/kubernetes.html.md b/website/source/docs/acl/auth-methods/kubernetes.html.md index 86a03ed62..cdd580499 100644 --- a/website/source/docs/acl/auth-methods/kubernetes.html.md +++ b/website/source/docs/acl/auth-methods/kubernetes.html.md @@ -34,6 +34,23 @@ parameters are required to properly configure an auth method of type - `ServiceAccountJWT` `(string: )` - A Service Account Token ([JWT](https://jwt.io/ "JSON Web Token")) used by the Consul leader to validate application JWTs during login. + +- `MapNamespaces` `(bool: )` - **(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: )` - **(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: )` - **(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