From f3f64af8218a87d5ad19e1bf59b98349b00a3278 Mon Sep 17 00:00:00 2001 From: Tim Gross Date: Wed, 25 Jan 2023 14:33:06 -0500 Subject: [PATCH] WI: allow workloads to use RPCs associated with HTTP API (#15870) This changeset allows Workload Identities to authenticate to all the RPCs that support HTTP API endpoints, for use with PR #15864. * Extends the work done for pre-forwarding authentication to all RPCs that support a HTTP API endpoint. * Consolidates the auth helpers used by the CSI, Service Registration, and Node endpoints that are currently used to support both tokens and client secrets. Intentionally excluded from this changeset: * The Variables endpoint still has custom handling because of the implicit policies. Ideally we'll figure out an efficient way to resolve those into real policies and then we can get rid of that custom handling. * The RPCs that don't currently support auth tokens (i.e. those that don't support HTTP endpoints) have not been updated with the new pre-forwarding auth We'll be doing this under a separate PR to support RPC rate metrics. --- .changelog/15870.txt | 3 + .semgrep/rpc_endpoint.yml | 10 +- nomad/acl.go | 44 ++++++- nomad/acl_endpoint.go | 142 +++++++++++++++++----- nomad/acl_endpoint_test.go | 2 +- nomad/alloc_endpoint.go | 55 +++++---- nomad/alloc_endpoint_test.go | 4 +- nomad/client_agent_endpoint.go | 21 +++- nomad/client_alloc_endpoint.go | 50 ++++++-- nomad/client_fs_endpoint.go | 30 ++++- nomad/client_stats_endpoint.go | 7 +- nomad/csi_endpoint.go | 161 ++++++++++++++----------- nomad/deployment_endpoint.go | 57 +++++++-- nomad/eval_endpoint.go | 41 +++++-- nomad/event_endpoint.go | 6 + nomad/job_endpoint.go | 120 +++++++++++++++--- nomad/keyring_endpoint.go | 29 ++++- nomad/namespace_endpoint.go | 32 ++++- nomad/node_endpoint.go | 76 +++++++----- nomad/operator_endpoint.go | 72 +++++++++-- nomad/operator_endpoint_test.go | 4 +- nomad/periodic_endpoint.go | 7 +- nomad/scaling_endpoint.go | 14 ++- nomad/search_endpoint.go | 14 ++- nomad/service_registration_endpoint.go | 98 +++++---------- nomad/status_endpoint.go | 6 +- nomad/system_endpoint.go | 14 ++- nomad/variables_endpoint.go | 16 +-- 28 files changed, 811 insertions(+), 324 deletions(-) create mode 100644 .changelog/15870.txt diff --git a/.changelog/15870.txt b/.changelog/15870.txt new file mode 100644 index 000000000..fb0e720c8 --- /dev/null +++ b/.changelog/15870.txt @@ -0,0 +1,3 @@ +```release-note:improvement +identity: Allow workloads to use RPCs associated with HTTP API +``` diff --git a/.semgrep/rpc_endpoint.yml b/.semgrep/rpc_endpoint.yml index 9488dc63a..6fe60bc29 100644 --- a/.semgrep/rpc_endpoint.yml +++ b/.semgrep/rpc_endpoint.yml @@ -68,8 +68,16 @@ rules: authErr := $A.$B.Authenticate($A.ctx, args) ... if authErr != nil { - return authErr + return $C } + ... + - pattern-not-inside: | + authErr := $A.$B.Authenticate(nil, args) + ... + if authErr != nil { + return $C + } + ... - metavariable-pattern: metavariable: $METHOD patterns: diff --git a/nomad/acl.go b/nomad/acl.go index c7ecae1e7..f658a7b54 100644 --- a/nomad/acl.go +++ b/nomad/acl.go @@ -156,7 +156,33 @@ func (s *Server) remoteIPFromRPCContext(ctx *RPCContext) (net.IP, error) { return nil, structs.ErrPermissionDenied } -func (s *Server) ResolveACL(aclToken *structs.ACLToken) (*acl.ACL, error) { +// ResolveACL is an authentication wrapper which handles resolving both ACL +// tokens and Workload Identities. If both are provided the ACL token is +// preferred, but it is best for the RPC caller to only include the credentials +// for the identity they intend the operation to be performed with. +func (s *Server) ResolveACL(args structs.RequestWithIdentity) (*acl.ACL, error) { + identity := args.GetIdentity() + if !s.config.ACLEnabled || identity == nil { + return nil, nil + } + aclToken := identity.GetACLToken() + if aclToken != nil { + return s.ResolveACLForToken(aclToken) + } + claims := identity.GetClaims() + if claims != nil { + return s.ResolveClaims(claims) + } + return nil, nil +} + +// ResolveACLForToken resolves an ACL from a token only. It should be used only +// by Variables endpoints, which have additional implicit policies for their +// claims so we can't wrap them up in ResolveACL. +// +// TODO: figure out a way to the Variables endpoint implicit policies baked into +// their acl.ACL object so that we can avoid using this method. +func (s *Server) ResolveACLForToken(aclToken *structs.ACLToken) (*acl.ACL, error) { if !s.config.ACLEnabled { return nil, nil } @@ -167,6 +193,22 @@ func (s *Server) ResolveACL(aclToken *structs.ACLToken) (*acl.ACL, error) { return resolveACLFromToken(snap, s.aclCache, aclToken) } +// ResolveClientOrACL resolves an ACL if the identity has a token or claim, and +// falls back to verifying the client ID if one has been set +func (s *Server) ResolveClientOrACL(args structs.RequestWithIdentity) (*acl.ACL, error) { + identity := args.GetIdentity() + if !s.config.ACLEnabled || identity == nil || identity.ClientID != "" { + return nil, nil + } + aclObj, err := s.ResolveACL(args) + if err != nil { + return nil, err + } + + // Returns either the users aclObj, or nil if ACLs are disabled. + return aclObj, nil +} + // ResolveToken is used to translate an ACL Token Secret ID into // an ACL object, nil if ACLs are disabled, or an error. func (s *Server) ResolveToken(secretID string) (*acl.ACL, error) { diff --git a/nomad/acl_endpoint.go b/nomad/acl_endpoint.go index 456f667da..b3b099804 100644 --- a/nomad/acl_endpoint.go +++ b/nomad/acl_endpoint.go @@ -73,15 +73,19 @@ func (a *ACL) UpsertPolicies(args *structs.ACLPolicyUpsertRequest, reply *struct if !a.srv.config.ACLEnabled { return aclDisabled } + authErr := a.srv.Authenticate(a.ctx, args) args.Region = a.srv.config.AuthoritativeRegion if done, err := a.srv.forward("ACL.UpsertPolicies", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "upsert_policies"}, time.Now()) // Check management level permissions - if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if acl, err := a.srv.ResolveACL(args); err != nil { return err } else if acl == nil || !acl.IsManagement() { return structs.ErrPermissionDenied @@ -117,15 +121,19 @@ func (a *ACL) DeletePolicies(args *structs.ACLPolicyDeleteRequest, reply *struct if !a.srv.config.ACLEnabled { return aclDisabled } + authErr := a.srv.Authenticate(a.ctx, args) args.Region = a.srv.config.AuthoritativeRegion if done, err := a.srv.forward("ACL.DeletePolicies", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "delete_policies"}, time.Now()) // Check management level permissions - if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if acl, err := a.srv.ResolveACL(args); err != nil { return err } else if acl == nil || !acl.IsManagement() { return structs.ErrPermissionDenied @@ -152,14 +160,17 @@ func (a *ACL) ListPolicies(args *structs.ACLPolicyListRequest, reply *structs.AC if !a.srv.config.ACLEnabled { return aclDisabled } - + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward("ACL.ListPolicies", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "list_policies"}, time.Now()) // Check management level permissions - acl, err := a.srv.ResolveToken(args.AuthToken) + acl, err := a.srv.ResolveACL(args) if err != nil { return err } else if acl == nil { @@ -242,13 +253,17 @@ func (a *ACL) GetPolicy(args *structs.ACLPolicySpecificRequest, reply *structs.S return aclDisabled } + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward("ACL.GetPolicy", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "get_policy"}, time.Now()) // Check management level permissions - acl, err := a.srv.ResolveToken(args.AuthToken) + acl, err := a.srv.ResolveACL(args) if err != nil { return err } else if acl == nil { @@ -513,7 +528,7 @@ func (a *ACL) UpsertTokens(args *structs.ACLTokenUpsertRequest, reply *structs.A if !a.srv.config.ACLEnabled { return aclDisabled } - + authErr := a.srv.Authenticate(a.ctx, args) // Validate non-zero set of tokens if len(args.Tokens) == 0 { return structs.NewErrRPCCoded(http.StatusBadRequest, "must specify as least one token") @@ -545,10 +560,13 @@ func (a *ACL) UpsertTokens(args *structs.ACLTokenUpsertRequest, reply *structs.A if done, err := a.srv.forward(structs.ACLUpsertTokensRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "upsert_tokens"}, time.Now()) // Check management level permissions - if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if acl, err := a.srv.ResolveACL(args); err != nil { return err } else if acl == nil || !acl.IsManagement() { return structs.ErrPermissionDenied @@ -674,7 +692,7 @@ func (a *ACL) DeleteTokens(args *structs.ACLTokenDeleteRequest, reply *structs.G if !a.srv.config.ACLEnabled { return aclDisabled } - + authErr := a.srv.Authenticate(a.ctx, args) // Validate non-zero set of tokens if len(args.AccessorIDs) == 0 { return structs.NewErrRPCCoded(400, "must specify as least one token") @@ -683,10 +701,13 @@ func (a *ACL) DeleteTokens(args *structs.ACLTokenDeleteRequest, reply *structs.G if done, err := a.srv.forward("ACL.DeleteTokens", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "delete_tokens"}, time.Now()) // Check management level permissions - if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if acl, err := a.srv.ResolveACL(args); err != nil { return err } else if acl == nil || !acl.IsManagement() { return structs.ErrPermissionDenied @@ -753,13 +774,17 @@ func (a *ACL) ListTokens(args *structs.ACLTokenListRequest, reply *structs.ACLTo if !a.srv.config.ACLEnabled { return aclDisabled } + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward("ACL.ListTokens", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "list_tokens"}, time.Now()) // Check management level permissions - if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if acl, err := a.srv.ResolveACL(args); err != nil { return err } else if acl == nil || !acl.IsManagement() { return structs.ErrPermissionDenied @@ -837,12 +862,16 @@ func (a *ACL) GetToken(args *structs.ACLTokenSpecificRequest, reply *structs.Sin if !a.srv.config.ACLEnabled { return aclDisabled } + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward("ACL.GetToken", args, args, reply); done { return err } + if authErr != nil { + return authErr + } defer metrics.MeasureSince([]string{"nomad", "acl", "get_token"}, time.Now()) - acl, err := a.srv.ResolveToken(args.AuthToken) + acl, err := a.srv.ResolveACL(args) if err != nil { return err } @@ -898,13 +927,17 @@ func (a *ACL) GetTokens(args *structs.ACLTokenSetRequest, reply *structs.ACLToke if !a.srv.config.ACLEnabled { return aclDisabled } + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward("ACL.GetTokens", args, args, reply); done { return err } + if authErr != nil { + return authErr + } defer metrics.MeasureSince([]string{"nomad", "acl", "get_tokens"}, time.Now()) // Check management level permissions - if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if acl, err := a.srv.ResolveACL(args); err != nil { return err } else if acl == nil || !acl.IsManagement() { return structs.ErrPermissionDenied @@ -1134,7 +1167,7 @@ func (a *ACL) UpsertRoles( if !a.srv.config.ACLEnabled { return aclDisabled } - + authErr := a.srv.Authenticate(a.ctx, args) // This endpoint always forwards to the authoritative region as ACL roles // are global. args.Region = a.srv.config.AuthoritativeRegion @@ -1142,6 +1175,9 @@ func (a *ACL) UpsertRoles( if done, err := a.srv.forward(structs.ACLUpsertRolesRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "upsert_roles"}, time.Now()) // ACL roles can only be used once all servers, in all federated regions @@ -1151,8 +1187,8 @@ func (a *ACL) UpsertRoles( minACLRoleVersion) } - // Only tokens with management level permissions can create ACL roles. - if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil { + // Only management level permissions can create ACL roles. + if acl, err := a.srv.ResolveACL(args); err != nil { return err } else if acl == nil || !acl.IsManagement() { return structs.ErrPermissionDenied @@ -1278,6 +1314,8 @@ func (a *ACL) DeleteRolesByID( return aclDisabled } + authErr := a.srv.Authenticate(a.ctx, args) + // This endpoint always forwards to the authoritative region as ACL roles // are global. args.Region = a.srv.config.AuthoritativeRegion @@ -1285,6 +1323,9 @@ func (a *ACL) DeleteRolesByID( if done, err := a.srv.forward(structs.ACLDeleteRolesByIDRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "delete_roles"}, time.Now()) // ACL roles can only be used once all servers, in all federated regions @@ -1294,8 +1335,8 @@ func (a *ACL) DeleteRolesByID( minACLRoleVersion) } - // Only tokens with management level permissions can create ACL roles. - if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil { + // Only management level permissions can create ACL roles. + if acl, err := a.srv.ResolveACL(args); err != nil { return err } else if acl == nil || !acl.IsManagement() { return structs.ErrPermissionDenied @@ -1330,13 +1371,17 @@ func (a *ACL) ListRoles( return aclDisabled } + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward(structs.ACLListRolesRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "list_roles"}, time.Now()) // Resolve the token and ensure it has some form of permissions. - acl, err := a.srv.ResolveToken(args.AuthToken) + acl, err := a.srv.ResolveACL(args) if err != nil { return err } else if acl == nil { @@ -1481,13 +1526,17 @@ func (a *ACL) GetRoleByID( return aclDisabled } + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward(structs.ACLGetRoleByIDRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "get_role_id"}, time.Now()) // Resolve the token and ensure it has some form of permissions. - acl, err := a.srv.ResolveToken(args.AuthToken) + acl, err := a.srv.ResolveACL(args) if err != nil { return err } else if acl == nil { @@ -1565,14 +1614,17 @@ func (a *ACL) GetRoleByName( if !a.srv.config.ACLEnabled { return aclDisabled } - + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward(structs.ACLGetRoleByNameRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "get_role_name"}, time.Now()) // Resolve the token and ensure it has some form of permissions. - acl, err := a.srv.ResolveToken(args.AuthToken) + acl, err := a.srv.ResolveACL(args) if err != nil { return err } else if acl == nil { @@ -1706,11 +1758,15 @@ func (a *ACL) UpsertAuthMethods( if !a.srv.config.ACLEnabled { return aclDisabled } + authErr := a.srv.Authenticate(a.ctx, args) args.Region = a.srv.config.AuthoritativeRegion if done, err := a.srv.forward(structs.ACLUpsertAuthMethodsRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "upsert_auth_methods"}, time.Now()) // ACL auth methods can only be used once all servers in all federated @@ -1721,7 +1777,7 @@ func (a *ACL) UpsertAuthMethods( } // Check management level permissions - if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if acl, err := a.srv.ResolveACL(args); err != nil { return err } else if acl == nil || !acl.IsManagement() { return structs.ErrPermissionDenied @@ -1810,10 +1866,14 @@ func (a *ACL) DeleteAuthMethods( } args.Region = a.srv.config.AuthoritativeRegion + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward( structs.ACLDeleteAuthMethodsRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "delete_auth_methods_by_name"}, time.Now()) // ACL auth methods can only be used once all servers in all federated @@ -1824,7 +1884,7 @@ func (a *ACL) DeleteAuthMethods( } // Check management level permissions - if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if acl, err := a.srv.ResolveACL(args); err != nil { return err } else if acl == nil || !acl.IsManagement() { return structs.ErrPermissionDenied @@ -1906,14 +1966,18 @@ func (a *ACL) GetAuthMethod( return aclDisabled } + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward( structs.ACLGetAuthMethodRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "get_auth_method_name"}, time.Now()) // Resolve the token and ensure it has some form of permissions. - acl, err := a.srv.ResolveToken(args.AuthToken) + acl, err := a.srv.ResolveACL(args) if err != nil { return err } else if acl == nil || !acl.IsManagement() { @@ -2037,9 +2101,13 @@ func (a *ACL) UpsertBindingRules( } args.Region = a.srv.config.AuthoritativeRegion + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward(structs.ACLUpsertBindingRulesRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "upsert_binding_rules"}, time.Now()) // ACL binding rules can only be used once all servers in all federated @@ -2050,7 +2118,7 @@ func (a *ACL) UpsertBindingRules( } // Check management level permissions - if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if acl, err := a.srv.ResolveACL(args); err != nil { return err } else if acl == nil || !acl.IsManagement() { return structs.ErrPermissionDenied @@ -2164,9 +2232,13 @@ func (a *ACL) DeleteBindingRules( } args.Region = a.srv.config.AuthoritativeRegion + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward(structs.ACLDeleteBindingRulesRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "delete_binding_rules"}, time.Now()) // ACL binding rules can only be used once all servers in all federated @@ -2177,7 +2249,7 @@ func (a *ACL) DeleteBindingRules( } // Check management level permissions. - if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if acl, err := a.srv.ResolveACL(args); err != nil { return err } else if acl == nil || !acl.IsManagement() { return structs.ErrPermissionDenied @@ -2213,13 +2285,17 @@ func (a *ACL) ListBindingRules( return aclDisabled } + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward(structs.ACLListBindingRulesRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "list_binding_rules"}, time.Now()) // Check management level permissions. - if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if acl, err := a.srv.ResolveACL(args); err != nil { return err } else if acl == nil || !acl.IsManagement() { return structs.ErrPermissionDenied @@ -2267,13 +2343,17 @@ func (a *ACL) GetBindingRules( return aclDisabled } + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward(structs.ACLGetBindingRulesRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "get_rules"}, time.Now()) // Check management level permissions. - if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if acl, err := a.srv.ResolveACL(args); err != nil { return err } else if acl == nil || !acl.IsManagement() { return structs.ErrPermissionDenied @@ -2317,13 +2397,17 @@ func (a *ACL) GetBindingRule( return aclDisabled } + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward(structs.ACLGetBindingRuleRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "acl", "get_binding_rule"}, time.Now()) // Check management level permissions. - if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if acl, err := a.srv.ResolveACL(args); err != nil { return err } else if acl == nil || !acl.IsManagement() { return structs.ErrPermissionDenied diff --git a/nomad/acl_endpoint_test.go b/nomad/acl_endpoint_test.go index 4121135a7..2207f9ba8 100644 --- a/nomad/acl_endpoint_test.go +++ b/nomad/acl_endpoint_test.go @@ -2234,7 +2234,7 @@ func TestACL_ListRoles(t *testing.T) { } var aclRoleResp1 structs.ACLRolesListResponse err := msgpackrpc.CallWithCodec(codec, structs.ACLListRolesRPCMethod, aclRoleReq1, &aclRoleResp1) - require.ErrorContains(t, err, "ACL token not found") + require.ErrorContains(t, err, structs.ErrPermissionDenied.Error()) // Try listing roles with a valid ACL token. aclRoleReq2 := &structs.ACLRolesListRequest{ diff --git a/nomad/alloc_endpoint.go b/nomad/alloc_endpoint.go index 44a6a93af..162daba46 100644 --- a/nomad/alloc_endpoint.go +++ b/nomad/alloc_endpoint.go @@ -8,7 +8,6 @@ import ( "github.com/armon/go-metrics" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-memdb" - "github.com/hashicorp/go-multierror" "github.com/hashicorp/nomad/acl" "github.com/hashicorp/nomad/helper/pointer" @@ -31,15 +30,19 @@ func NewAllocEndpoint(srv *Server, ctx *RPCContext) *Alloc { // List is used to list the allocations in the system func (a *Alloc) List(args *structs.AllocListRequest, reply *structs.AllocListResponse) error { + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward("Alloc.List", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "alloc", "list"}, time.Now()) namespace := args.RequestNamespace() // Check namespace read-job permissions - aclObj, err := a.srv.ResolveToken(args.AuthToken) + aclObj, err := a.srv.ResolveACL(args) if err != nil { return err } @@ -136,34 +139,20 @@ func (a *Alloc) List(args *structs.AllocListRequest, reply *structs.AllocListRes // GetAlloc is used to lookup a particular allocation func (a *Alloc) GetAlloc(args *structs.AllocSpecificRequest, reply *structs.SingleAllocResponse) error { + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward("Alloc.GetAlloc", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "alloc", "get_alloc"}, time.Now()) // Check namespace read-job permissions before performing blocking query. allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob) - aclObj, err := a.srv.ResolveToken(args.AuthToken) + aclObj, err := a.srv.ResolveClientOrACL(args) if err != nil { - // If ResolveToken had an unexpected error return that - if err != structs.ErrTokenNotFound { - return err - } - - // Attempt to lookup AuthToken as a Node.SecretID since nodes - // call this endpoint and don't have an ACL token. - node, stateErr := a.srv.fsm.State().NodeBySecretID(nil, args.AuthToken) - if stateErr != nil { - // Return the original ResolveToken error with this err - var merr multierror.Error - merr.Errors = append(merr.Errors, err, stateErr) - return merr.ErrorOrNil() - } - - // Not a node or a valid ACL token - if node == nil { - return structs.ErrTokenNotFound - } + return err } // Setup the blocking query @@ -276,9 +265,15 @@ func (a *Alloc) GetAllocs(args *structs.AllocsGetRequest, // Stop is used to stop an allocation and migrate it to another node. func (a *Alloc) Stop(args *structs.AllocStopRequest, reply *structs.AllocStopResponse) error { + + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward("Alloc.Stop", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } + defer metrics.MeasureSince([]string{"nomad", "alloc", "stop"}, time.Now()) alloc, err := getAlloc(a.srv.State(), args.AllocID) @@ -288,7 +283,7 @@ func (a *Alloc) Stop(args *structs.AllocStopRequest, reply *structs.AllocStopRes // Check for namespace alloc-lifecycle permissions. allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityAllocLifecycle) - aclObj, err := a.srv.ResolveToken(args.AuthToken) + aclObj, err := a.srv.ResolveACL(args) if err != nil { return err } else if !allowNsOp(aclObj, alloc.Namespace) { @@ -335,13 +330,18 @@ func (a *Alloc) Stop(args *structs.AllocStopRequest, reply *structs.AllocStopRes // UpdateDesiredTransition is used to update the desired transitions of an // allocation. func (a *Alloc) UpdateDesiredTransition(args *structs.AllocUpdateDesiredTransitionRequest, reply *structs.GenericResponse) error { + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward("Alloc.UpdateDesiredTransition", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } + defer metrics.MeasureSince([]string{"nomad", "alloc", "update_desired_transition"}, time.Now()) // Check that it is a management token. - if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := a.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.IsManagement() { return structs.ErrPermissionDenied @@ -370,14 +370,19 @@ func (a *Alloc) GetServiceRegistrations( args *structs.AllocServiceRegistrationsRequest, reply *structs.AllocServiceRegistrationsResponse) error { + authErr := a.srv.Authenticate(a.ctx, args) if done, err := a.srv.forward(structs.AllocServiceRegistrationsRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } + defer metrics.MeasureSince([]string{"nomad", "alloc", "get_service_registrations"}, time.Now()) // If ACLs are enabled, ensure the caller has the read-job namespace // capability. - aclObj, err := a.srv.ResolveToken(args.AuthToken) + aclObj, err := a.srv.ResolveACL(args) if err != nil { return err } else if aclObj != nil { diff --git a/nomad/alloc_endpoint_test.go b/nomad/alloc_endpoint_test.go index 3ed12965b..2b48c06b3 100644 --- a/nomad/alloc_endpoint_test.go +++ b/nomad/alloc_endpoint_test.go @@ -1279,7 +1279,7 @@ func TestAllocEndpoint_List_AllNamespaces_ACL_OSS(t *testing.T) { Namespace: "*", Token: uuid.Generate(), Error: true, - Message: structs.ErrTokenNotFound.Error(), + Message: structs.ErrPermissionDenied.Error(), }, { Label: "all namespaces with insufficient token", @@ -1311,7 +1311,7 @@ func TestAllocEndpoint_List_AllNamespaces_ACL_OSS(t *testing.T) { Namespace: ns1.Name, Token: uuid.Generate(), Error: true, - Message: structs.ErrTokenNotFound.Error(), + Message: structs.ErrPermissionDenied.Error(), }, { Label: "bad namespace with root token", diff --git a/nomad/client_agent_endpoint.go b/nomad/client_agent_endpoint.go index 59402ab6b..beecad4c9 100644 --- a/nomad/client_agent_endpoint.go +++ b/nomad/client_agent_endpoint.go @@ -35,8 +35,13 @@ func (a *Agent) register() { } func (a *Agent) Profile(args *structs.AgentPprofRequest, reply *structs.AgentPprofResponse) error { + authErr := a.srv.Authenticate(nil, args) + if authErr != nil { + return structs.ErrPermissionDenied + } + // Check ACL for agent write - aclObj, err := a.srv.ResolveToken(args.AuthToken) + aclObj, err := a.srv.ResolveACL(args) if err != nil { return err } else if aclObj != nil && !aclObj.AllowAgentWrite() { @@ -128,9 +133,14 @@ func (a *Agent) monitor(conn io.ReadWriteCloser) { handleStreamResultError(err, pointer.Of(int64(500)), encoder) return } + authErr := a.srv.Authenticate(nil, &args) + if authErr != nil { + handleStreamResultError(structs.ErrPermissionDenied, nil, encoder) + return + } // Check agent read permissions - if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := a.srv.ResolveACL(&args); err != nil { handleStreamResultError(err, nil, encoder) return } else if aclObj != nil && !aclObj.AllowAgentRead() { @@ -398,8 +408,11 @@ func (a *Agent) forwardProfileClient(args *structs.AgentPprofRequest, reply *str // Host returns data about the agent's host system for the `debug` command. func (a *Agent) Host(args *structs.HostDataRequest, reply *structs.HostDataResponse) error { - - aclObj, err := a.srv.ResolveToken(args.AuthToken) + authErr := a.srv.Authenticate(nil, args) + if authErr != nil { + return structs.ErrPermissionDenied + } + aclObj, err := a.srv.ResolveACL(args) if err != nil { return err } diff --git a/nomad/client_alloc_endpoint.go b/nomad/client_alloc_endpoint.go index dc4e8700b..22a3fd145 100644 --- a/nomad/client_alloc_endpoint.go +++ b/nomad/client_alloc_endpoint.go @@ -39,14 +39,19 @@ func (a *ClientAllocations) GarbageCollectAll(args *structs.NodeSpecificRequest, // in the forwarding chain. args.QueryOptions.AllowStale = true + authErr := a.srv.Authenticate(nil, args) + // Potentially forward to a different region. if done, err := a.srv.forward("ClientAllocations.GarbageCollectAll", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "client_allocations", "garbage_collect_all"}, time.Now()) // Check node read permissions - if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := a.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNodeWrite() { return structs.ErrPermissionDenied @@ -85,10 +90,15 @@ func (a *ClientAllocations) Signal(args *structs.AllocSignalRequest, reply *stru // in the forwarding chain. args.QueryOptions.AllowStale = true + authErr := a.srv.Authenticate(nil, args) + // Potentially forward to a different region. if done, err := a.srv.forward("ClientAllocations.Signal", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "client_allocations", "signal"}, time.Now()) // Verify the arguments. @@ -108,7 +118,7 @@ func (a *ClientAllocations) Signal(args *structs.AllocSignalRequest, reply *stru } // Check namespace alloc-lifecycle permission. - if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := a.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityAllocLifecycle) { return structs.ErrPermissionDenied @@ -137,10 +147,15 @@ func (a *ClientAllocations) GarbageCollect(args *structs.AllocSpecificRequest, r // in the forwarding chain. args.QueryOptions.AllowStale = true + authErr := a.srv.Authenticate(nil, args) + // Potentially forward to a different region. if done, err := a.srv.forward("ClientAllocations.GarbageCollect", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "client_allocations", "garbage_collect"}, time.Now()) // Verify the arguments. @@ -160,7 +175,7 @@ func (a *ClientAllocations) GarbageCollect(args *structs.AllocSpecificRequest, r } // Check namespace submit-job permission. - if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := a.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilitySubmitJob) { return structs.ErrPermissionDenied @@ -189,10 +204,15 @@ func (a *ClientAllocations) Restart(args *structs.AllocRestartRequest, reply *st // in the forwarding chain. args.QueryOptions.AllowStale = true + authErr := a.srv.Authenticate(nil, args) + // Potentially forward to a different region. if done, err := a.srv.forward("ClientAllocations.Restart", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "client_allocations", "restart"}, time.Now()) // Find the allocation @@ -207,7 +227,7 @@ func (a *ClientAllocations) Restart(args *structs.AllocRestartRequest, reply *st } // Check for namespace alloc-lifecycle permissions. - if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := a.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityAllocLifecycle) { return structs.ErrPermissionDenied @@ -236,10 +256,15 @@ func (a *ClientAllocations) Stats(args *cstructs.AllocStatsRequest, reply *cstru // in the forwarding chain. args.QueryOptions.AllowStale = true + authErr := a.srv.Authenticate(nil, args) + // Potentially forward to a different region. if done, err := a.srv.forward("ClientAllocations.Stats", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "client_allocations", "stats"}, time.Now()) // Find the allocation @@ -254,7 +279,7 @@ func (a *ClientAllocations) Stats(args *cstructs.AllocStatsRequest, reply *cstru } // Check for namespace read-job permissions. - if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := a.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadJob) { return structs.ErrPermissionDenied @@ -287,10 +312,15 @@ func (a *ClientAllocations) Checks(args *cstructs.AllocChecksRequest, reply *cst // hop in the forwarding chain. args.QueryOptions.AllowStale = true + authErr := a.srv.Authenticate(nil, args) + // Potentially forward to a different region. if done, err := a.srv.forward("ClientAllocations.Checks", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "client_allocations", "checks"}, time.Now()) // Grab the state snapshot, as we need this to perform lookups for a number @@ -308,7 +338,7 @@ func (a *ClientAllocations) Checks(args *cstructs.AllocChecksRequest, reply *cst } // Check for namespace read-job permissions. - if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := a.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadJob) { return structs.ErrPermissionDenied @@ -344,12 +374,18 @@ func (a *ClientAllocations) exec(conn io.ReadWriteCloser) { return } + authErr := a.srv.Authenticate(nil, &args) + // Check if we need to forward to a different region if r := args.RequestRegion(); r != a.srv.Region() { forwardRegionStreamingRpc(a.srv, conn, encoder, &args, "Allocations.Exec", args.AllocID, &args.QueryOptions) return } + if authErr != nil { + handleStreamResultError(structs.ErrPermissionDenied, nil, encoder) + return + } // Verify the arguments. if args.AllocID == "" { @@ -375,7 +411,7 @@ func (a *ClientAllocations) exec(conn io.ReadWriteCloser) { } // Check node read permissions - if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := a.srv.ResolveACL(&args); err != nil { handleStreamResultError(err, nil, encoder) return } else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityAllocExec) { diff --git a/nomad/client_fs_endpoint.go b/nomad/client_fs_endpoint.go index 946b2748d..e7faab3b7 100644 --- a/nomad/client_fs_endpoint.go +++ b/nomad/client_fs_endpoint.go @@ -106,10 +106,15 @@ func (f *FileSystem) List(args *cstructs.FsListRequest, reply *cstructs.FsListRe // in the forwarding chain. args.QueryOptions.AllowStale = true + authErr := f.srv.Authenticate(nil, args) + // Potentially forward to a different region. if done, err := f.srv.forward("FileSystem.List", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "file_system", "list"}, time.Now()) // Verify the arguments. @@ -130,7 +135,7 @@ func (f *FileSystem) List(args *cstructs.FsListRequest, reply *cstructs.FsListRe // Check namespace filesystem read permissions allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadFS) - aclObj, err := f.srv.ResolveToken(args.AuthToken) + aclObj, err := f.srv.ResolveACL(args) if err != nil { return err } else if !allowNsOp(aclObj, alloc.Namespace) { @@ -160,10 +165,15 @@ func (f *FileSystem) Stat(args *cstructs.FsStatRequest, reply *cstructs.FsStatRe // in the forwarding chain. args.QueryOptions.AllowStale = true + authErr := f.srv.Authenticate(nil, args) + // Potentially forward to a different region. if done, err := f.srv.forward("FileSystem.Stat", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "file_system", "stat"}, time.Now()) // Verify the arguments. @@ -183,7 +193,7 @@ func (f *FileSystem) Stat(args *cstructs.FsStatRequest, reply *cstructs.FsStatRe } // Check filesystem read permissions - if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := f.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadFS) { return structs.ErrPermissionDenied @@ -221,12 +231,18 @@ func (f *FileSystem) stream(conn io.ReadWriteCloser) { return } + authErr := f.srv.Authenticate(nil, &args) + // Check if we need to forward to a different region if r := args.RequestRegion(); r != f.srv.Region() { forwardRegionStreamingRpc(f.srv, conn, encoder, &args, "FileSystem.Stream", args.AllocID, &args.QueryOptions) return } + if authErr != nil { + handleStreamResultError(structs.ErrPermissionDenied, nil, encoder) + return + } // Verify the arguments. if args.AllocID == "" { @@ -252,7 +268,7 @@ func (f *FileSystem) stream(conn io.ReadWriteCloser) { } // Check namespace read-fs permissions. - if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := f.srv.ResolveACL(&args); err != nil { handleStreamResultError(err, nil, encoder) return } else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadFS) { @@ -339,12 +355,18 @@ func (f *FileSystem) logs(conn io.ReadWriteCloser) { return } + authErr := f.srv.Authenticate(nil, &args) + // Check if we need to forward to a different region if r := args.RequestRegion(); r != f.srv.Region() { forwardRegionStreamingRpc(f.srv, conn, encoder, &args, "FileSystem.Logs", args.AllocID, &args.QueryOptions) return } + if authErr != nil { + handleStreamResultError(structs.ErrPermissionDenied, nil, encoder) + return + } // Verify the arguments. if args.AllocID == "" { @@ -372,7 +394,7 @@ func (f *FileSystem) logs(conn io.ReadWriteCloser) { // Check namespace read-logs *or* read-fs permissions. allowNsOp := acl.NamespaceValidator( acl.NamespaceCapabilityReadFS, acl.NamespaceCapabilityReadLogs) - aclObj, err := f.srv.ResolveToken(args.AuthToken) + aclObj, err := f.srv.ResolveACL(&args) if err != nil { handleStreamResultError(err, nil, encoder) return diff --git a/nomad/client_stats_endpoint.go b/nomad/client_stats_endpoint.go index ac1976f90..fbf4ac9e4 100644 --- a/nomad/client_stats_endpoint.go +++ b/nomad/client_stats_endpoint.go @@ -23,19 +23,24 @@ func NewClientStatsEndpoint(srv *Server) *ClientStats { } func (s *ClientStats) Stats(args *nstructs.NodeSpecificRequest, reply *structs.ClientStatsResponse) error { + // We only allow stale reads since the only potentially stale information is // the Node registration and the cost is fairly high for adding another hope // in the forwarding chain. args.QueryOptions.AllowStale = true + authErr := s.srv.Authenticate(nil, args) // Potentially forward to a different region. if done, err := s.srv.forward("ClientStats.Stats", args, args, reply); done { return err } + if authErr != nil { + return nstructs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "client_stats", "stats"}, time.Now()) // Check node read permissions - if aclObj, err := s.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := s.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNodeRead() { return nstructs.ErrPermissionDenied diff --git a/nomad/csi_endpoint.go b/nomad/csi_endpoint.go index edaaf1caf..391e2103b 100644 --- a/nomad/csi_endpoint.go +++ b/nomad/csi_endpoint.go @@ -29,54 +29,6 @@ func NewCSIVolumeEndpoint(srv *Server, ctx *RPCContext) *CSIVolume { return &CSIVolume{srv: srv, ctx: ctx, logger: srv.logger.Named("csi_volume")} } -// QueryACLObj looks up the ACL token in the request and returns the acl.ACL object -// - fallback to node secret ids -func (s *Server) QueryACLObj(args *structs.QueryOptions, allowNodeAccess bool) (*acl.ACL, error) { - // Lookup the token - aclObj, err := s.ResolveToken(args.AuthToken) - if err != nil { - // If ResolveToken had an unexpected error return that - if !structs.IsErrTokenNotFound(err) { - return nil, err - } - - // If we don't allow access to this endpoint from Nodes, then return token - // not found. - if !allowNodeAccess { - return nil, structs.ErrTokenNotFound - } - - ws := memdb.NewWatchSet() - // Attempt to lookup AuthToken as a Node.SecretID since nodes may call - // call this endpoint and don't have an ACL token. - node, stateErr := s.fsm.State().NodeBySecretID(ws, args.AuthToken) - if stateErr != nil { - // Return the original ResolveToken error with this err - var merr multierror.Error - merr.Errors = append(merr.Errors, err, stateErr) - return nil, merr.ErrorOrNil() - } - - // We did not find a Node for this ID, so return Token Not Found. - if node == nil { - return nil, structs.ErrTokenNotFound - } - } - - // Return either the users aclObj, or nil if ACLs are disabled. - return aclObj, nil -} - -// WriteACLObj calls QueryACLObj for a WriteRequest -func (s *Server) WriteACLObj(args *structs.WriteRequest, allowNodeAccess bool) (*acl.ACL, error) { - opts := &structs.QueryOptions{ - Region: args.RequestRegion(), - Namespace: args.RequestNamespace(), - AuthToken: args.AuthToken, - } - return s.QueryACLObj(opts, allowNodeAccess) -} - const ( csiVolumeTable = "csi_volumes" csiPluginTable = "csi_plugins" @@ -99,15 +51,20 @@ func (s *Server) replySetIndex(table string, reply *structs.QueryMeta) error { // List replies with CSIVolumes, filtered by ACL access func (v *CSIVolume) List(args *structs.CSIVolumeListRequest, reply *structs.CSIVolumeListResponse) error { + + authErr := v.srv.Authenticate(v.ctx, args) if done, err := v.srv.forward("CSIVolume.List", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIListVolume, acl.NamespaceCapabilityCSIReadVolume, acl.NamespaceCapabilityCSIMountVolume, acl.NamespaceCapabilityListJobs) - aclObj, err := v.srv.QueryACLObj(&args.QueryOptions, false) + aclObj, err := v.srv.ResolveACL(args) if err != nil { return err } @@ -211,14 +168,19 @@ func (v *CSIVolume) List(args *structs.CSIVolumeListRequest, reply *structs.CSIV // Get fetches detailed information about a specific volume func (v *CSIVolume) Get(args *structs.CSIVolumeGetRequest, reply *structs.CSIVolumeGetResponse) error { + + authErr := v.srv.Authenticate(v.ctx, args) if done, err := v.srv.forward("CSIVolume.Get", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } allowCSIAccess := acl.NamespaceValidator(acl.NamespaceCapabilityCSIReadVolume, acl.NamespaceCapabilityCSIMountVolume, acl.NamespaceCapabilityReadJob) - aclObj, err := v.srv.QueryACLObj(&args.QueryOptions, true) + aclObj, err := v.srv.ResolveClientOrACL(args) if err != nil { return err } @@ -309,12 +271,17 @@ func (v *CSIVolume) controllerValidateVolume(req *structs.CSIVolumeRegisterReque // again with the right settings. This lets us be as strict with // validation here as the CreateVolume CSI RPC is expected to be. func (v *CSIVolume) Register(args *structs.CSIVolumeRegisterRequest, reply *structs.CSIVolumeRegisterResponse) error { + + authErr := v.srv.Authenticate(v.ctx, args) if done, err := v.srv.forward("CSIVolume.Register", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIWriteVolume) - aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, false) + aclObj, err := v.srv.ResolveACL(args) if err != nil { return err } @@ -400,12 +367,17 @@ func (v *CSIVolume) Register(args *structs.CSIVolumeRegisterRequest, reply *stru // Deregister removes a set of volumes func (v *CSIVolume) Deregister(args *structs.CSIVolumeDeregisterRequest, reply *structs.CSIVolumeDeregisterResponse) error { + + authErr := v.srv.Authenticate(v.ctx, args) if done, err := v.srv.forward("CSIVolume.Deregister", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIWriteVolume) - aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, false) + aclObj, err := v.srv.ResolveACL(args) if err != nil { return err } @@ -437,12 +409,17 @@ func (v *CSIVolume) Deregister(args *structs.CSIVolumeDeregisterRequest, reply * // Claim submits a change to a volume claim func (v *CSIVolume) Claim(args *structs.CSIVolumeClaimRequest, reply *structs.CSIVolumeClaimResponse) error { + + authErr := v.srv.Authenticate(v.ctx, args) if done, err := v.srv.forward("CSIVolume.Claim", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIMountVolume) - aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, true) + aclObj, err := v.srv.ResolveClientOrACL(args) if err != nil { return err } @@ -619,14 +596,19 @@ func allowCSIMount(aclObj *acl.ACL, namespace string) bool { // ControllerUnpublish RPCs to the client. It handles errors according to the // current claim state. func (v *CSIVolume) Unpublish(args *structs.CSIVolumeUnpublishRequest, reply *structs.CSIVolumeUnpublishResponse) error { + + authErr := v.srv.Authenticate(v.ctx, args) if done, err := v.srv.forward("CSIVolume.Unpublish", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "volume", "unpublish"}, time.Now()) allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIMountVolume) - aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, true) + aclObj, err := v.srv.ResolveClientOrACL(args) if err != nil { return err } @@ -957,14 +939,17 @@ func (v *CSIVolume) checkpointClaim(vol *structs.CSIVolume, claim *structs.CSIVo func (v *CSIVolume) Create(args *structs.CSIVolumeCreateRequest, reply *structs.CSIVolumeCreateResponse) error { + authErr := v.srv.Authenticate(v.ctx, args) if done, err := v.srv.forward("CSIVolume.Create", args, args, reply); done { return err } - + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "volume", "create"}, time.Now()) allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIWriteVolume) - aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, false) + aclObj, err := v.srv.ResolveACL(args) if err != nil { return err } @@ -1081,14 +1066,18 @@ func (v *CSIVolume) createVolume(vol *structs.CSIVolume, plugin *structs.CSIPlug } func (v *CSIVolume) Delete(args *structs.CSIVolumeDeleteRequest, reply *structs.CSIVolumeDeleteResponse) error { + + authErr := v.srv.Authenticate(v.ctx, args) if done, err := v.srv.forward("CSIVolume.Delete", args, args, reply); done { return err } - + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "volume", "delete"}, time.Now()) allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIWriteVolume) - aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, false) + aclObj, err := v.srv.ResolveACL(args) if err != nil { return err } @@ -1161,16 +1150,20 @@ func (v *CSIVolume) deleteVolume(vol *structs.CSIVolume, plugin *structs.CSIPlug func (v *CSIVolume) ListExternal(args *structs.CSIVolumeExternalListRequest, reply *structs.CSIVolumeExternalListResponse) error { + authErr := v.srv.Authenticate(v.ctx, args) if done, err := v.srv.forward("CSIVolume.ListExternal", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "volume", "list_external"}, time.Now()) allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIListVolume, acl.NamespaceCapabilityCSIReadVolume, acl.NamespaceCapabilityCSIMountVolume, acl.NamespaceCapabilityListJobs) - aclObj, err := v.srv.QueryACLObj(&args.QueryOptions, false) + aclObj, err := v.srv.ResolveACL(args) if err != nil { return err } @@ -1221,13 +1214,17 @@ func (v *CSIVolume) ListExternal(args *structs.CSIVolumeExternalListRequest, rep func (v *CSIVolume) CreateSnapshot(args *structs.CSISnapshotCreateRequest, reply *structs.CSISnapshotCreateResponse) error { + authErr := v.srv.Authenticate(v.ctx, args) if done, err := v.srv.forward("CSIVolume.CreateSnapshot", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "volume", "create_snapshot"}, time.Now()) allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIWriteVolume) - aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, false) + aclObj, err := v.srv.ResolveACL(args) if err != nil { return err } @@ -1312,13 +1309,17 @@ func (v *CSIVolume) CreateSnapshot(args *structs.CSISnapshotCreateRequest, reply func (v *CSIVolume) DeleteSnapshot(args *structs.CSISnapshotDeleteRequest, reply *structs.CSISnapshotDeleteResponse) error { + authErr := v.srv.Authenticate(v.ctx, args) if done, err := v.srv.forward("CSIVolume.DeleteSnapshot", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "volume", "delete_snapshot"}, time.Now()) allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIWriteVolume) - aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, false) + aclObj, err := v.srv.ResolveACL(args) if err != nil { return err } @@ -1372,16 +1373,20 @@ func (v *CSIVolume) DeleteSnapshot(args *structs.CSISnapshotDeleteRequest, reply func (v *CSIVolume) ListSnapshots(args *structs.CSISnapshotListRequest, reply *structs.CSISnapshotListResponse) error { + authErr := v.srv.Authenticate(v.ctx, args) if done, err := v.srv.forward("CSIVolume.ListSnapshots", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "volume", "list_snapshots"}, time.Now()) allowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIListVolume, acl.NamespaceCapabilityCSIReadVolume, acl.NamespaceCapabilityCSIMountVolume, acl.NamespaceCapabilityListJobs) - aclObj, err := v.srv.QueryACLObj(&args.QueryOptions, false) + aclObj, err := v.srv.ResolveACL(args) if err != nil { return err } @@ -1444,21 +1449,24 @@ func NewCSIPluginEndpoint(srv *Server, ctx *RPCContext) *CSIPlugin { // List replies with CSIPlugins, filtered by ACL access func (v *CSIPlugin) List(args *structs.CSIPluginListRequest, reply *structs.CSIPluginListResponse) error { + + authErr := v.srv.Authenticate(v.ctx, args) if done, err := v.srv.forward("CSIPlugin.List", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } + defer metrics.MeasureSince([]string{"nomad", "plugin", "list"}, time.Now()) - aclObj, err := v.srv.QueryACLObj(&args.QueryOptions, false) + aclObj, err := v.srv.ResolveACL(args) if err != nil { return err } - if !aclObj.AllowPluginList() { return structs.ErrPermissionDenied } - defer metrics.MeasureSince([]string{"nomad", "plugin", "list"}, time.Now()) - opts := blockingOptions{ queryOpts: &args.QueryOptions, queryMeta: &reply.QueryMeta, @@ -1499,15 +1507,20 @@ func (v *CSIPlugin) List(args *structs.CSIPluginListRequest, reply *structs.CSIP // Get fetches detailed information about a specific plugin func (v *CSIPlugin) Get(args *structs.CSIPluginGetRequest, reply *structs.CSIPluginGetResponse) error { + + authErr := v.srv.Authenticate(v.ctx, args) if done, err := v.srv.forward("CSIPlugin.Get", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } + defer metrics.MeasureSince([]string{"nomad", "plugin", "get"}, time.Now()) - aclObj, err := v.srv.QueryACLObj(&args.QueryOptions, false) + aclObj, err := v.srv.ResolveACL(args) if err != nil { return err } - if !aclObj.AllowPluginRead() { return structs.ErrPermissionDenied } @@ -1515,8 +1528,6 @@ func (v *CSIPlugin) Get(args *structs.CSIPluginGetRequest, reply *structs.CSIPlu withAllocs := aclObj == nil || aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) - defer metrics.MeasureSince([]string{"nomad", "plugin", "get"}, time.Now()) - if args.ID == "" { return fmt.Errorf("missing plugin ID") } @@ -1564,19 +1575,23 @@ func (v *CSIPlugin) Get(args *structs.CSIPluginGetRequest, reply *structs.CSIPlu // Delete deletes a plugin if it is unused func (v *CSIPlugin) Delete(args *structs.CSIPluginDeleteRequest, reply *structs.CSIPluginDeleteResponse) error { + + authErr := v.srv.Authenticate(v.ctx, args) if done, err := v.srv.forward("CSIPlugin.Delete", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } + defer metrics.MeasureSince([]string{"nomad", "plugin", "delete"}, time.Now()) // Check that it is a management token. - if aclObj, err := v.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := v.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.IsManagement() { return structs.ErrPermissionDenied } - defer metrics.MeasureSince([]string{"nomad", "plugin", "delete"}, time.Now()) - if args.ID == "" { return fmt.Errorf("missing plugin ID") } diff --git a/nomad/deployment_endpoint.go b/nomad/deployment_endpoint.go index b7d5cbbd0..e398203b5 100644 --- a/nomad/deployment_endpoint.go +++ b/nomad/deployment_endpoint.go @@ -42,7 +42,7 @@ func (d *Deployment) GetDeployment(args *structs.DeploymentSpecificRequest, // Check namespace read-job permissions allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob) - aclObj, err := d.srv.ResolveACL(args.GetIdentity().GetACLToken()) + aclObj, err := d.srv.ResolveACL(args) if err != nil { return err } else if !allowNsOp(aclObj, args.RequestNamespace()) { @@ -93,9 +93,14 @@ func (d *Deployment) GetDeployment(args *structs.DeploymentSpecificRequest, // Fail is used to force fail a deployment func (d *Deployment) Fail(args *structs.DeploymentFailRequest, reply *structs.DeploymentUpdateResponse) error { + + authErr := d.srv.Authenticate(d.ctx, args) if done, err := d.srv.forward("Deployment.Fail", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "deployment", "fail"}, time.Now()) // Validate the arguments @@ -119,7 +124,7 @@ func (d *Deployment) Fail(args *structs.DeploymentFailRequest, reply *structs.De } // Check namespace submit-job permissions - if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := d.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) { return structs.ErrPermissionDenied @@ -135,9 +140,13 @@ func (d *Deployment) Fail(args *structs.DeploymentFailRequest, reply *structs.De // Pause is used to pause a deployment func (d *Deployment) Pause(args *structs.DeploymentPauseRequest, reply *structs.DeploymentUpdateResponse) error { + authErr := d.srv.Authenticate(d.ctx, args) if done, err := d.srv.forward("Deployment.Pause", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "deployment", "pause"}, time.Now()) // Validate the arguments @@ -161,7 +170,7 @@ func (d *Deployment) Pause(args *structs.DeploymentPauseRequest, reply *structs. } // Check namespace submit-job permissions - if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := d.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) { return structs.ErrPermissionDenied @@ -181,9 +190,13 @@ func (d *Deployment) Pause(args *structs.DeploymentPauseRequest, reply *structs. // Promote is used to promote canaries in a deployment func (d *Deployment) Promote(args *structs.DeploymentPromoteRequest, reply *structs.DeploymentUpdateResponse) error { + authErr := d.srv.Authenticate(d.ctx, args) if done, err := d.srv.forward("Deployment.Promote", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "deployment", "promote"}, time.Now()) // Validate the arguments @@ -207,7 +220,7 @@ func (d *Deployment) Promote(args *structs.DeploymentPromoteRequest, reply *stru } // Check namespace submit-job permissions - if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := d.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) { return structs.ErrPermissionDenied @@ -223,9 +236,13 @@ func (d *Deployment) Promote(args *structs.DeploymentPromoteRequest, reply *stru // Run is used to start a pending deployment func (d *Deployment) Run(args *structs.DeploymentRunRequest, reply *structs.DeploymentUpdateResponse) error { + authErr := d.srv.Authenticate(d.ctx, args) if done, err := d.srv.forward("Deployment.Run", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "deployment", "run"}, time.Now()) // Validate the arguments @@ -249,7 +266,7 @@ func (d *Deployment) Run(args *structs.DeploymentRunRequest, reply *structs.Depl } // Check namespace submit-job permissions - if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := d.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) { return structs.ErrPermissionDenied @@ -265,9 +282,13 @@ func (d *Deployment) Run(args *structs.DeploymentRunRequest, reply *structs.Depl // Unblock is used to unblock a deployment func (d *Deployment) Unblock(args *structs.DeploymentUnblockRequest, reply *structs.DeploymentUpdateResponse) error { + authErr := d.srv.Authenticate(d.ctx, args) if done, err := d.srv.forward("Deployment.Unblock", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "deployment", "unblock"}, time.Now()) // Validate the arguments @@ -291,7 +312,7 @@ func (d *Deployment) Unblock(args *structs.DeploymentUnblockRequest, reply *stru } // Check namespace submit-job permissions - if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := d.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) { return structs.ErrPermissionDenied @@ -307,9 +328,13 @@ func (d *Deployment) Unblock(args *structs.DeploymentUnblockRequest, reply *stru // Cancel is used to cancel a deployment func (d *Deployment) Cancel(args *structs.DeploymentCancelRequest, reply *structs.DeploymentUpdateResponse) error { + authErr := d.srv.Authenticate(d.ctx, args) if done, err := d.srv.forward("Deployment.Cancel", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "deployment", "cancel"}, time.Now()) // Validate the arguments @@ -333,7 +358,7 @@ func (d *Deployment) Cancel(args *structs.DeploymentCancelRequest, reply *struct } // Check namespace submit-job permissions - if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := d.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) { return structs.ErrPermissionDenied @@ -350,9 +375,13 @@ func (d *Deployment) Cancel(args *structs.DeploymentCancelRequest, reply *struct // SetAllocHealth is used to set the health of allocations that are part of the // deployment. func (d *Deployment) SetAllocHealth(args *structs.DeploymentAllocHealthRequest, reply *structs.DeploymentUpdateResponse) error { + authErr := d.srv.Authenticate(d.ctx, args) if done, err := d.srv.forward("Deployment.SetAllocHealth", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "deployment", "set_alloc_health"}, time.Now()) // Validate the arguments @@ -380,7 +409,7 @@ func (d *Deployment) SetAllocHealth(args *structs.DeploymentAllocHealthRequest, } // Check namespace submit-job permissions - if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := d.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) { return structs.ErrPermissionDenied @@ -396,16 +425,20 @@ func (d *Deployment) SetAllocHealth(args *structs.DeploymentAllocHealthRequest, // List returns the list of deployments in the system func (d *Deployment) List(args *structs.DeploymentListRequest, reply *structs.DeploymentListResponse) error { + authErr := d.srv.Authenticate(d.ctx, args) if done, err := d.srv.forward("Deployment.List", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "deployment", "list"}, time.Now()) namespace := args.RequestNamespace() // Check namespace read-job permissions against request namespace since // results are filtered by request namespace. - if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := d.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadJob) { return structs.ErrPermissionDenied @@ -483,16 +516,20 @@ func (d *Deployment) List(args *structs.DeploymentListRequest, reply *structs.De // Allocations returns the list of allocations that are a part of the deployment func (d *Deployment) Allocations(args *structs.DeploymentSpecificRequest, reply *structs.AllocListResponse) error { + authErr := d.srv.Authenticate(d.ctx, args) if done, err := d.srv.forward("Deployment.Allocations", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "deployment", "allocations"}, time.Now()) // Check namespace read-job permissions against the request namespace. // Must re-check against the alloc namespace when they return to ensure // there's no namespace mismatch. allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob) - aclObj, err := d.srv.ResolveToken(args.AuthToken) + aclObj, err := d.srv.ResolveACL(args) if err != nil { return err } else if !allowNsOp(aclObj, args.RequestNamespace()) { diff --git a/nomad/eval_endpoint.go b/nomad/eval_endpoint.go index 702b8b442..a1d06ef8c 100644 --- a/nomad/eval_endpoint.go +++ b/nomad/eval_endpoint.go @@ -41,14 +41,19 @@ func NewEvalEndpoint(srv *Server, ctx *RPCContext) *Eval { // GetEval is used to request information about a specific evaluation func (e *Eval) GetEval(args *structs.EvalSpecificRequest, reply *structs.SingleEvalResponse) error { + + authErr := e.srv.Authenticate(e.ctx, args) if done, err := e.srv.forward("Eval.GetEval", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "eval", "get_eval"}, time.Now()) // Check for read-job permissions before performing blocking query. allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob) - aclObj, err := e.srv.ResolveToken(args.AuthToken) + aclObj, err := e.srv.ResolveACL(args) if err != nil { return err } else if !allowNsOp(aclObj, args.RequestNamespace()) { @@ -112,9 +117,6 @@ func (e *Eval) Dequeue(args *structs.EvalDequeueRequest, reply *structs.EvalDequeueResponse) error { authErr := e.srv.Authenticate(e.ctx, args) - if authErr != nil { - return authErr - } // Ensure the connection was initiated by another server if TLS is used. err := validateTLSCertificateLevel(e.srv, e.ctx, tlsCertificateLevelServer) @@ -125,6 +127,10 @@ func (e *Eval) Dequeue(args *structs.EvalDequeueRequest, if done, err := e.srv.forward("Eval.Dequeue", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } + defer metrics.MeasureSince([]string{"nomad", "eval", "dequeue"}, time.Now()) // Ensure there is at least one scheduler @@ -443,14 +449,18 @@ func (e *Eval) Delete( args *structs.EvalDeleteRequest, reply *structs.EvalDeleteResponse) error { + authErr := e.srv.Authenticate(e.ctx, args) if done, err := e.srv.forward(structs.EvalDeleteRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "eval", "delete"}, time.Now()) // This RPC endpoint is very destructive and alters Nomad's core state, // meaning only those with management tokens can call it. - if aclObj, err := e.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := e.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.IsManagement() { return structs.ErrPermissionDenied @@ -629,15 +639,20 @@ func (e *Eval) deleteEvalsByFilter(args *structs.EvalDeleteRequest) (int, uint64 // List is used to get a list of the evaluations in the system func (e *Eval) List(args *structs.EvalListRequest, reply *structs.EvalListResponse) error { + + authErr := e.srv.Authenticate(e.ctx, args) if done, err := e.srv.forward("Eval.List", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "eval", "list"}, time.Now()) namespace := args.RequestNamespace() // Check for read-job permissions - aclObj, err := e.srv.ResolveToken(args.AuthToken) + aclObj, err := e.srv.ResolveACL(args) if err != nil { return err } @@ -748,14 +763,19 @@ func (e *Eval) List(args *structs.EvalListRequest, reply *structs.EvalListRespon // Count is used to get a list of the evaluations in the system func (e *Eval) Count(args *structs.EvalCountRequest, reply *structs.EvalCountResponse) error { + + authErr := e.srv.Authenticate(e.ctx, args) if done, err := e.srv.forward("Eval.Count", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "eval", "count"}, time.Now()) namespace := args.RequestNamespace() // Check for read-job permissions - aclObj, err := e.srv.ResolveToken(args.AuthToken) + aclObj, err := e.srv.ResolveACL(args) if err != nil { return err } @@ -847,14 +867,19 @@ func (e *Eval) Count(args *structs.EvalCountRequest, reply *structs.EvalCountRes // Allocations is used to list the allocations for an evaluation func (e *Eval) Allocations(args *structs.EvalSpecificRequest, reply *structs.EvalAllocationsResponse) error { + + authErr := e.srv.Authenticate(e.ctx, args) if done, err := e.srv.forward("Eval.Allocations", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "eval", "allocations"}, time.Now()) // Check for read-job permissions allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob) - aclObj, err := e.srv.ResolveToken(args.AuthToken) + aclObj, err := e.srv.ResolveACL(args) if err != nil { return err } else if !allowNsOp(aclObj, args.RequestNamespace()) { diff --git a/nomad/event_endpoint.go b/nomad/event_endpoint.go index 4bb75c095..6535bb2db 100644 --- a/nomad/event_endpoint.go +++ b/nomad/event_endpoint.go @@ -37,6 +37,8 @@ func (e *Event) stream(conn io.ReadWriteCloser) { return } + authErr := e.srv.Authenticate(nil, &args) + // forward to appropriate region if args.Region != e.srv.config.Region { err := e.forwardStreamingRPC(args.Region, "Event.Stream", args, conn) @@ -46,6 +48,10 @@ func (e *Event) stream(conn io.ReadWriteCloser) { return } + if authErr != nil { + handleJsonResultError(structs.ErrPermissionDenied, pointer.Of(int64(403)), encoder) + } + // Generate the subscription request subReq := &stream.SubscribeRequest{ Token: args.AuthToken, diff --git a/nomad/job_endpoint.go b/nomad/job_endpoint.go index dec4b9ca2..6eb12573a 100644 --- a/nomad/job_endpoint.go +++ b/nomad/job_endpoint.go @@ -84,9 +84,13 @@ func NewJobEndpoints(s *Server, ctx *RPCContext) *Job { // Register is used to upsert a job for scheduling func (j *Job) Register(args *structs.JobRegisterRequest, reply *structs.JobRegisterResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.Register", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "register"}, time.Now()) // Validate the arguments @@ -120,7 +124,7 @@ func (j *Job) Register(args *structs.JobRegisterRequest, reply *structs.JobRegis reply.Warnings = structs.MergeMultierrorWarnings(warnings...) // Check job submission permissions - aclObj, err := j.srv.ResolveToken(args.AuthToken) + aclObj, err := j.srv.ResolveACL(args) if err != nil { return err } else if aclObj != nil { @@ -483,13 +487,17 @@ func getSignalConstraint(signals []string) *structs.Constraint { // Summary retrieves the summary of a job. func (j *Job) Summary(args *structs.JobSummaryRequest, reply *structs.JobSummaryResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.Summary", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job_summary", "get_job_summary"}, time.Now()) // Check for read-job permissions - if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := j.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { return structs.ErrPermissionDenied @@ -531,9 +539,13 @@ func (j *Job) Summary(args *structs.JobSummaryRequest, reply *structs.JobSummary // Must forward to the leader, because only the leader will have a live Vault // client with which to validate vault tokens. func (j *Job) Validate(args *structs.JobValidateRequest, reply *structs.JobValidateResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.Validate", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "validate"}, time.Now()) // defensive check; http layer and RPC requester should ensure namespaces are set consistently @@ -548,7 +560,7 @@ func (j *Job) Validate(args *structs.JobValidateRequest, reply *structs.JobValid args.Job = job // Check for read-job permissions - if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := j.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { return structs.ErrPermissionDenied @@ -578,13 +590,17 @@ func (j *Job) Validate(args *structs.JobValidateRequest, reply *structs.JobValid // Revert is used to revert the job to a prior version func (j *Job) Revert(args *structs.JobRevertRequest, reply *structs.JobRegisterResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.Revert", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "revert"}, time.Now()) // Check for submit-job permissions - if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := j.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) { return structs.ErrPermissionDenied @@ -646,13 +662,17 @@ func (j *Job) Revert(args *structs.JobRevertRequest, reply *structs.JobRegisterR // Stable is used to mark the job version as stable func (j *Job) Stable(args *structs.JobStabilityRequest, reply *structs.JobStabilityResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.Stable", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "stable"}, time.Now()) // Check for read-job permissions - if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := j.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) { return structs.ErrPermissionDenied @@ -692,13 +712,17 @@ func (j *Job) Stable(args *structs.JobStabilityRequest, reply *structs.JobStabil // Evaluate is used to force a job for re-evaluation func (j *Job) Evaluate(args *structs.JobEvaluateRequest, reply *structs.JobRegisterResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.Evaluate", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "evaluate"}, time.Now()) // Check for read-job permissions - if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := j.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { return structs.ErrPermissionDenied @@ -788,13 +812,17 @@ func (j *Job) Evaluate(args *structs.JobEvaluateRequest, reply *structs.JobRegis // Deregister is used to remove a job the cluster. func (j *Job) Deregister(args *structs.JobDeregisterRequest, reply *structs.JobDeregisterResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.Deregister", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "deregister"}, time.Now()) // Check for submit-job permissions - if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := j.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) { return structs.ErrPermissionDenied @@ -901,13 +929,17 @@ func (j *Job) Deregister(args *structs.JobDeregisterRequest, reply *structs.JobD // BatchDeregister is used to remove a set of jobs from the cluster. func (j *Job) BatchDeregister(args *structs.JobBatchDeregisterRequest, reply *structs.JobBatchDeregisterResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.BatchDeregister", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "batch_deregister"}, time.Now()) // Resolve the ACL token - aclObj, err := j.srv.ResolveToken(args.AuthToken) + aclObj, err := j.srv.ResolveACL(args) if err != nil { return err } @@ -986,15 +1018,19 @@ func (j *Job) BatchDeregister(args *structs.JobBatchDeregisterRequest, reply *st // Scale is used to modify one of the scaling targets in the job func (j *Job) Scale(args *structs.JobScaleRequest, reply *structs.JobRegisterResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.Scale", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "scale"}, time.Now()) namespace := args.RequestNamespace() // Authorize request - aclObj, err := j.srv.ResolveToken(args.AuthToken) + aclObj, err := j.srv.ResolveACL(args) if err != nil { return err } @@ -1168,13 +1204,17 @@ func (j *Job) Scale(args *structs.JobScaleRequest, reply *structs.JobRegisterRes // GetJob is used to request information about a specific job func (j *Job) GetJob(args *structs.JobSpecificRequest, reply *structs.SingleJobResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.GetJob", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "get_job"}, time.Now()) // Check for read-job permissions - if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := j.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { return structs.ErrPermissionDenied @@ -1214,13 +1254,17 @@ func (j *Job) GetJob(args *structs.JobSpecificRequest, // GetJobVersions is used to retrieve all tracked versions of a job. func (j *Job) GetJobVersions(args *structs.JobVersionsRequest, reply *structs.JobVersionsResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.GetJobVersions", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "get_job_versions"}, time.Now()) // Check for read-job permissions - if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := j.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { return structs.ErrPermissionDenied @@ -1316,15 +1360,19 @@ func registrationsAreAllowed(aclObj *acl.ACL, state *state.StateStore) (bool, er // List is used to list the jobs registered in the system func (j *Job) List(args *structs.JobListRequest, reply *structs.JobListResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.List", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "list"}, time.Now()) namespace := args.RequestNamespace() // Check for list-job permissions - aclObj, err := j.srv.ResolveToken(args.AuthToken) + aclObj, err := j.srv.ResolveACL(args) if err != nil { return err } @@ -1422,13 +1470,17 @@ func (j *Job) List(args *structs.JobListRequest, reply *structs.JobListResponse) // Allocations is used to list the allocations for a job func (j *Job) Allocations(args *structs.JobSpecificRequest, reply *structs.JobAllocationsResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.Allocations", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "allocations"}, time.Now()) // Check for read-job permissions - if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := j.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { return structs.ErrPermissionDenied @@ -1477,13 +1529,17 @@ func (j *Job) Allocations(args *structs.JobSpecificRequest, // Evaluations is used to list the evaluations for a job func (j *Job) Evaluations(args *structs.JobSpecificRequest, reply *structs.JobEvaluationsResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.Evaluations", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "evaluations"}, time.Now()) // Check for read-job permissions - if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := j.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { return structs.ErrPermissionDenied @@ -1519,13 +1575,17 @@ func (j *Job) Evaluations(args *structs.JobSpecificRequest, // Deployments is used to list the deployments for a job func (j *Job) Deployments(args *structs.JobSpecificRequest, reply *structs.DeploymentListResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.Deployments", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "deployments"}, time.Now()) // Check for read-job permissions - if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := j.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { return structs.ErrPermissionDenied @@ -1561,13 +1621,17 @@ func (j *Job) Deployments(args *structs.JobSpecificRequest, // LatestDeployment is used to retrieve the latest deployment for a job func (j *Job) LatestDeployment(args *structs.JobSpecificRequest, reply *structs.SingleDeploymentResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.LatestDeployment", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "latest_deployment"}, time.Now()) // Check for read-job permissions - if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := j.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { return structs.ErrPermissionDenied @@ -1608,9 +1672,13 @@ func (j *Job) LatestDeployment(args *structs.JobSpecificRequest, // Plan is used to cause a dry-run evaluation of the Job and return the results // with a potential diff containing annotations. func (j *Job) Plan(args *structs.JobPlanRequest, reply *structs.JobPlanResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.Plan", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "plan"}, time.Now()) // Validate the arguments @@ -1629,7 +1697,7 @@ func (j *Job) Plan(args *structs.JobPlanRequest, reply *structs.JobPlanResponse) reply.Warnings = structs.MergeMultierrorWarnings(warnings...) // Check job submission permissions, which we assume is the same for plan - if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := j.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil { if !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) { @@ -1823,13 +1891,17 @@ func validateJobUpdate(old, new *structs.Job) error { // Dispatch a parameterized job. func (j *Job) Dispatch(args *structs.JobDispatchRequest, reply *structs.JobDispatchResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.Dispatch", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "dispatch"}, time.Now()) // Check for submit-job permissions - aclObj, err := j.srv.ResolveToken(args.AuthToken) + aclObj, err := j.srv.ResolveACL(args) if err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityDispatchJob) { @@ -2058,13 +2130,17 @@ func validateDispatchRequest(req *structs.JobDispatchRequest, job *structs.Job) func (j *Job) ScaleStatus(args *structs.JobScaleStatusRequest, reply *structs.JobScaleStatusResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward("Job.ScaleStatus", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "scale_status"}, time.Now()) // Check for autoscaler permissions - if aclObj, err := j.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := j.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil { hasReadJob := aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) @@ -2173,14 +2249,18 @@ func (j *Job) GetServiceRegistrations( args *structs.JobServiceRegistrationsRequest, reply *structs.JobServiceRegistrationsResponse) error { + authErr := j.srv.Authenticate(j.ctx, args) if done, err := j.srv.forward(structs.JobServiceRegistrationsRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "job", "get_service_registrations"}, time.Now()) // If ACLs are enabled, ensure the caller has the read-job namespace // capability. - aclObj, err := j.srv.ResolveToken(args.AuthToken) + aclObj, err := j.srv.ResolveACL(args) if err != nil { return err } else if aclObj != nil { diff --git a/nomad/keyring_endpoint.go b/nomad/keyring_endpoint.go index 114fb41db..7d0ea0395 100644 --- a/nomad/keyring_endpoint.go +++ b/nomad/keyring_endpoint.go @@ -27,13 +27,17 @@ func NewKeyringEndpoint(srv *Server, ctx *RPCContext, enc *Encrypter) *Keyring { } func (k *Keyring) Rotate(args *structs.KeyringRotateRootKeyRequest, reply *structs.KeyringRotateRootKeyResponse) error { + + authErr := k.srv.Authenticate(k.ctx, args) if done, err := k.srv.forward("Keyring.Rotate", args, args, reply); done { return err } - + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "keyring", "rotate"}, time.Now()) - if aclObj, err := k.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := k.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.IsManagement() { return structs.ErrPermissionDenied @@ -95,9 +99,14 @@ func (k *Keyring) Rotate(args *structs.KeyringRotateRootKeyRequest, reply *struc } func (k *Keyring) List(args *structs.KeyringListRootKeyMetaRequest, reply *structs.KeyringListRootKeyMetaResponse) error { + + authErr := k.srv.Authenticate(k.ctx, args) if done, err := k.srv.forward("Keyring.List", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "keyring", "list"}, time.Now()) @@ -106,7 +115,7 @@ func (k *Keyring) List(args *structs.KeyringListRootKeyMetaRequest, reply *struc // replication err := validateTLSCertificateLevel(k.srv, k.ctx, tlsCertificateLevelServer) if err != nil { - if aclObj, err := k.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := k.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.IsManagement() { return structs.ErrPermissionDenied @@ -148,13 +157,18 @@ func (k *Keyring) List(args *structs.KeyringListRootKeyMetaRequest, reply *struc // Update updates an existing key in the keyring, including both the // key material and metadata. func (k *Keyring) Update(args *structs.KeyringUpdateRootKeyRequest, reply *structs.KeyringUpdateRootKeyResponse) error { + + authErr := k.srv.Authenticate(k.ctx, args) if done, err := k.srv.forward("Keyring.Update", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "keyring", "update"}, time.Now()) - if aclObj, err := k.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := k.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.IsManagement() { return structs.ErrPermissionDenied @@ -290,13 +304,18 @@ func (k *Keyring) Get(args *structs.KeyringGetRootKeyRequest, reply *structs.Key } func (k *Keyring) Delete(args *structs.KeyringDeleteRootKeyRequest, reply *structs.KeyringDeleteRootKeyResponse) error { + + authErr := k.srv.Authenticate(k.ctx, args) if done, err := k.srv.forward("Keyring.Delete", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "keyring", "delete"}, time.Now()) - if aclObj, err := k.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := k.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.IsManagement() { return structs.ErrPermissionDenied diff --git a/nomad/namespace_endpoint.go b/nomad/namespace_endpoint.go index 9af07df66..a757cda59 100644 --- a/nomad/namespace_endpoint.go +++ b/nomad/namespace_endpoint.go @@ -32,13 +32,13 @@ func (n *Namespace) UpsertNamespaces(args *structs.NamespaceUpsertRequest, return err } if authErr != nil { - return authErr + return structs.ErrPermissionDenied } defer metrics.MeasureSince([]string{"nomad", "namespace", "upsert_namespaces"}, time.Now()) // Check management permissions - if aclObj, err := n.srv.ResolveACL(args.GetIdentity().GetACLToken()); err != nil { + if aclObj, err := n.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.IsManagement() { return structs.ErrPermissionDenied @@ -76,14 +76,19 @@ func (n *Namespace) UpsertNamespaces(args *structs.NamespaceUpsertRequest, // DeleteNamespaces is used to delete a namespace func (n *Namespace) DeleteNamespaces(args *structs.NamespaceDeleteRequest, reply *structs.GenericResponse) error { + + authErr := n.srv.Authenticate(n.ctx, args) args.Region = n.srv.config.AuthoritativeRegion if done, err := n.srv.forward("Namespace.DeleteNamespaces", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "namespace", "delete_namespaces"}, time.Now()) // Check management permissions - if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := n.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.IsManagement() { return structs.ErrPermissionDenied @@ -225,13 +230,18 @@ func (n *Namespace) namespaceTerminalInRegion(authToken, namespace, region strin // ListNamespaces is used to list the namespaces func (n *Namespace) ListNamespaces(args *structs.NamespaceListRequest, reply *structs.NamespaceListResponse) error { + + authErr := n.srv.Authenticate(n.ctx, args) if done, err := n.srv.forward("Namespace.ListNamespaces", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "namespace", "list_namespace"}, time.Now()) // Resolve token to acl to filter namespace list - aclObj, err := n.srv.ResolveToken(args.AuthToken) + aclObj, err := n.srv.ResolveACL(args) if err != nil { return err } @@ -286,13 +296,18 @@ func (n *Namespace) ListNamespaces(args *structs.NamespaceListRequest, reply *st // GetNamespace is used to get a specific namespace func (n *Namespace) GetNamespace(args *structs.NamespaceSpecificRequest, reply *structs.SingleNamespaceResponse) error { + + authErr := n.srv.Authenticate(n.ctx, args) if done, err := n.srv.forward("Namespace.GetNamespace", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "namespace", "get_namespace"}, time.Now()) // Check capabilities for the given namespace permissions - if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := n.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNamespace(args.Name) { return structs.ErrPermissionDenied @@ -334,13 +349,18 @@ func (n *Namespace) GetNamespace(args *structs.NamespaceSpecificRequest, reply * // GetNamespaces is used to get a set of namespaces func (n *Namespace) GetNamespaces(args *structs.NamespaceSetRequest, reply *structs.NamespaceSetResponse) error { + + authErr := n.srv.Authenticate(n.ctx, args) if done, err := n.srv.forward("Namespace.GetNamespaces", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "namespace", "get_namespaces"}, time.Now()) // Check management permissions - if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := n.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.IsManagement() { return structs.ErrPermissionDenied diff --git a/nomad/node_endpoint.go b/nomad/node_endpoint.go index 8ae5320b3..159c5346c 100644 --- a/nomad/node_endpoint.go +++ b/nomad/node_endpoint.go @@ -319,9 +319,13 @@ func (n *Node) constructNodeServerInfoResponse(nodeID string, snap *state.StateS // Deregister is used to remove a client from the cluster. If a client should // just be made unavailable for scheduling, a status update is preferred. func (n *Node) Deregister(args *structs.NodeDeregisterRequest, reply *structs.NodeUpdateResponse) error { + authErr := n.srv.Authenticate(n.ctx, args) if done, err := n.srv.forward("Node.Deregister", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "client", "deregister"}, time.Now()) if args.NodeID == "" { @@ -341,9 +345,13 @@ func (n *Node) Deregister(args *structs.NodeDeregisterRequest, reply *structs.No // BatchDeregister is used to remove client nodes from the cluster. func (n *Node) BatchDeregister(args *structs.NodeBatchDeregisterRequest, reply *structs.NodeUpdateResponse) error { + authErr := n.srv.Authenticate(n.ctx, args) if done, err := n.srv.forward("Node.BatchDeregister", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "client", "batch_deregister"}, time.Now()) if len(args.NodeIDs) == 0 { @@ -361,7 +369,7 @@ func (n *Node) deregister(args *structs.NodeBatchDeregisterRequest, raftApplyFn func() (interface{}, uint64, error), ) error { // Check request permissions - if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := n.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNodeWrite() { return structs.ErrPermissionDenied @@ -614,13 +622,18 @@ func nodeStatusTransitionRequiresEval(newStatus, oldStatus string) bool { // UpdateDrain is used to update the drain mode of a client node func (n *Node) UpdateDrain(args *structs.NodeUpdateDrainRequest, reply *structs.NodeDrainUpdateResponse) error { + + authErr := n.srv.Authenticate(n.ctx, args) if done, err := n.srv.forward("Node.UpdateDrain", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "client", "update_drain"}, time.Now()) // Check node write permissions - if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := n.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNodeWrite() { return structs.ErrPermissionDenied @@ -708,13 +721,18 @@ func (n *Node) UpdateDrain(args *structs.NodeUpdateDrainRequest, // UpdateEligibility is used to update the scheduling eligibility of a node func (n *Node) UpdateEligibility(args *structs.NodeUpdateEligibilityRequest, reply *structs.NodeEligibilityUpdateResponse) error { + + authErr := n.srv.Authenticate(n.ctx, args) if done, err := n.srv.forward("Node.UpdateEligibility", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "client", "update_eligibility"}, time.Now()) // Check node write permissions - if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := n.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNodeWrite() { return structs.ErrPermissionDenied @@ -805,13 +823,18 @@ func (n *Node) UpdateEligibility(args *structs.NodeUpdateEligibilityRequest, // Evaluate is used to force a re-evaluation of the node func (n *Node) Evaluate(args *structs.NodeEvaluateRequest, reply *structs.NodeUpdateResponse) error { + + authErr := n.srv.Authenticate(n.ctx, args) if done, err := n.srv.forward("Node.Evaluate", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "client", "evaluate"}, time.Now()) // Check node write permissions - if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := n.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNodeWrite() { return structs.ErrPermissionDenied @@ -860,33 +883,22 @@ func (n *Node) Evaluate(args *structs.NodeEvaluateRequest, reply *structs.NodeUp // GetNode is used to request information about a specific node func (n *Node) GetNode(args *structs.NodeSpecificRequest, reply *structs.SingleNodeResponse) error { + + authErr := n.srv.Authenticate(n.ctx, args) if done, err := n.srv.forward("Node.GetNode", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "client", "get_node"}, time.Now()) // Check node read permissions - if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil { - // If ResolveToken had an unexpected error return that - if err != structs.ErrTokenNotFound { - return err - } - - // Attempt to lookup AuthToken as a Node.SecretID since nodes - // call this endpoint and don't have an ACL token. - node, stateErr := n.srv.fsm.State().NodeBySecretID(nil, args.AuthToken) - if stateErr != nil { - // Return the original ResolveToken error with this err - var merr multierror.Error - merr.Errors = append(merr.Errors, err, stateErr) - return merr.ErrorOrNil() - } - - // Not a node or a valid ACL token - if node == nil { - return structs.ErrTokenNotFound - } - } else if aclObj != nil && !aclObj.AllowNodeRead() { + aclObj, err := n.srv.ResolveClientOrACL(args) + if err != nil { + return err + } + if aclObj != nil && !aclObj.AllowNodeRead() { return structs.ErrPermissionDenied } @@ -931,13 +943,18 @@ func (n *Node) GetNode(args *structs.NodeSpecificRequest, // GetAllocs is used to request allocations for a specific node func (n *Node) GetAllocs(args *structs.NodeSpecificRequest, reply *structs.NodeAllocsResponse) error { + + authErr := n.srv.Authenticate(n.ctx, args) if done, err := n.srv.forward("Node.GetAllocs", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "client", "get_allocs"}, time.Now()) // Check node read and namespace job read permissions - aclObj, err := n.srv.ResolveToken(args.AuthToken) + aclObj, err := n.srv.ResolveACL(args) if err != nil { return err } @@ -1431,13 +1448,18 @@ func (n *Node) batchUpdate(future *structs.BatchFuture, updates []*structs.Alloc // List is used to list the available nodes func (n *Node) List(args *structs.NodeListRequest, reply *structs.NodeListResponse) error { + + authErr := n.srv.Authenticate(n.ctx, args) if done, err := n.srv.forward("Node.List", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "client", "list"}, time.Now()) // Check node read permissions - if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := n.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNodeRead() { return structs.ErrPermissionDenied diff --git a/nomad/operator_endpoint.go b/nomad/operator_endpoint.go index cf348f274..56e801a49 100644 --- a/nomad/operator_endpoint.go +++ b/nomad/operator_endpoint.go @@ -35,12 +35,17 @@ func (op *Operator) register() { // RaftGetConfiguration is used to retrieve the current Raft configuration. func (op *Operator) RaftGetConfiguration(args *structs.GenericRequest, reply *structs.RaftConfigurationResponse) error { + + authErr := op.srv.Authenticate(op.ctx, args) if done, err := op.srv.forward("Operator.RaftGetConfiguration", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } // Check management permissions - if aclObj, err := op.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := op.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.IsManagement() { return structs.ErrPermissionDenied @@ -96,12 +101,17 @@ func (op *Operator) RaftGetConfiguration(args *structs.GenericRequest, reply *st // "IP:port". The reply argument is not used, but it required to fulfill the RPC // interface. func (op *Operator) RaftRemovePeerByAddress(args *structs.RaftPeerByAddressRequest, reply *struct{}) error { + + authErr := op.srv.Authenticate(op.ctx, args) if done, err := op.srv.forward("Operator.RaftRemovePeerByAddress", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } // Check management permissions - if aclObj, err := op.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := op.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.IsManagement() { return structs.ErrPermissionDenied @@ -148,12 +158,17 @@ REMOVE: // "IP:port". The reply argument is not used, but is required to fulfill the RPC // interface. func (op *Operator) RaftRemovePeerByID(args *structs.RaftPeerByIDRequest, reply *struct{}) error { + + authErr := op.srv.Authenticate(op.ctx, args) if done, err := op.srv.forward("Operator.RaftRemovePeerByID", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } // Check management permissions - if aclObj, err := op.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := op.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.IsManagement() { return structs.ErrPermissionDenied @@ -209,12 +224,17 @@ REMOVE: // AutopilotGetConfiguration is used to retrieve the current Autopilot configuration. func (op *Operator) AutopilotGetConfiguration(args *structs.GenericRequest, reply *structs.AutopilotConfig) error { + + authErr := op.srv.Authenticate(op.ctx, args) if done, err := op.srv.forward("Operator.AutopilotGetConfiguration", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } // This action requires operator read access. - rule, err := op.srv.ResolveToken(args.AuthToken) + rule, err := op.srv.ResolveACL(args) if err != nil { return err } @@ -238,12 +258,17 @@ func (op *Operator) AutopilotGetConfiguration(args *structs.GenericRequest, repl // AutopilotSetConfiguration is used to set the current Autopilot configuration. func (op *Operator) AutopilotSetConfiguration(args *structs.AutopilotSetConfigRequest, reply *bool) error { + + authErr := op.srv.Authenticate(op.ctx, args) if done, err := op.srv.forward("Operator.AutopilotSetConfiguration", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } // This action requires operator write access. - rule, err := op.srv.ResolveToken(args.AuthToken) + rule, err := op.srv.ResolveACL(args) if err != nil { return err } @@ -275,15 +300,20 @@ func (op *Operator) AutopilotSetConfiguration(args *structs.AutopilotSetConfigRe // ServerHealth is used to get the current health of the servers. func (op *Operator) ServerHealth(args *structs.GenericRequest, reply *structs.OperatorHealthReply) error { + + authErr := op.srv.Authenticate(op.ctx, args) // This must be sent to the leader, so we fix the args since we are // re-using a structure where we don't support all the options. args.AllowStale = false if done, err := op.srv.forward("Operator.ServerHealth", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } // This action requires operator read access. - rule, err := op.srv.ResolveToken(args.AuthToken) + rule, err := op.srv.ResolveACL(args) if err != nil { return err } @@ -307,12 +337,17 @@ func (op *Operator) ServerHealth(args *structs.GenericRequest, reply *structs.Op // SchedulerSetConfiguration is used to set the current Scheduler configuration. func (op *Operator) SchedulerSetConfiguration(args *structs.SchedulerSetConfigRequest, reply *structs.SchedulerSetConfigurationResponse) error { + + authErr := op.srv.Authenticate(op.ctx, args) if done, err := op.srv.forward("Operator.SchedulerSetConfiguration", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } // This action requires operator write access. - rule, err := op.srv.ResolveToken(args.AuthToken) + rule, err := op.srv.ResolveACL(args) if err != nil { return err } else if rule != nil && !rule.AllowOperatorWrite() { @@ -356,12 +391,17 @@ func (op *Operator) SchedulerSetConfiguration(args *structs.SchedulerSetConfigRe // SchedulerGetConfiguration is used to retrieve the current Scheduler configuration. func (op *Operator) SchedulerGetConfiguration(args *structs.GenericRequest, reply *structs.SchedulerConfigurationResponse) error { + + authErr := op.srv.Authenticate(op.ctx, args) if done, err := op.srv.forward("Operator.SchedulerGetConfiguration", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } // This action requires operator read access. - rule, err := op.srv.ResolveToken(args.AuthToken) + rule, err := op.srv.ResolveACL(args) if err != nil { return err } else if rule != nil && !rule.AllowOperatorRead() { @@ -429,6 +469,8 @@ func (op *Operator) snapshotSave(conn io.ReadWriteCloser) { return } + authErr := op.srv.Authenticate(nil, &args) + // Forward to appropriate region if args.Region != op.srv.Region() { err := op.forwardStreamingRPC(args.Region, "Operator.SnapshotSave", args, conn) @@ -455,8 +497,12 @@ func (op *Operator) snapshotSave(conn io.ReadWriteCloser) { } } + if authErr != nil { + handleFailure(403, structs.ErrPermissionDenied) + } + // Check agent permissions - if aclObj, err := op.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := op.srv.ResolveACL(&args); err != nil { code := 500 if err == structs.ErrTokenNotFound { code = 400 @@ -511,6 +557,8 @@ func (op *Operator) snapshotRestore(conn io.ReadWriteCloser) { return } + authErr := op.srv.Authenticate(nil, &args) + // Forward to appropriate region if args.Region != op.srv.Region() { err := op.forwardStreamingRPC(args.Region, "Operator.SnapshotRestore", args, conn) @@ -535,8 +583,12 @@ func (op *Operator) snapshotRestore(conn io.ReadWriteCloser) { } + if authErr != nil { + handleFailure(403, structs.ErrPermissionDenied) + } + // Check agent permissions - if aclObj, err := op.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := op.srv.ResolveACL(&args); err != nil { code := 500 if err == structs.ErrTokenNotFound { code = 400 diff --git a/nomad/operator_endpoint_test.go b/nomad/operator_endpoint_test.go index 47a886467..d751f7cc2 100644 --- a/nomad/operator_endpoint_test.go +++ b/nomad/operator_endpoint_test.go @@ -664,7 +664,7 @@ func TestOperator_SnapshotSave_ACL(t *testing.T) { }{ {"root", root.SecretID, 0, nil}, {"no_permission_token", deniedToken.SecretID, 403, structs.ErrPermissionDenied}, - {"invalid token", uuid.Generate(), 400, structs.ErrTokenNotFound}, + {"invalid token", uuid.Generate(), 403, structs.ErrPermissionDenied}, {"unauthenticated", "", 403, structs.ErrPermissionDenied}, } @@ -886,7 +886,7 @@ func TestOperator_SnapshotRestore_ACL(t *testing.T) { }{ {"root", 0, nil}, {"no_permission_token", 403, structs.ErrPermissionDenied}, - {"invalid token", 400, structs.ErrTokenNotFound}, + {"invalid token", 403, structs.ErrPermissionDenied}, {"unauthenticated", 403, structs.ErrPermissionDenied}, } diff --git a/nomad/periodic_endpoint.go b/nomad/periodic_endpoint.go index e4d9d8a36..bad7b1c08 100644 --- a/nomad/periodic_endpoint.go +++ b/nomad/periodic_endpoint.go @@ -25,13 +25,18 @@ func NewPeriodicEndpoint(srv *Server, ctx *RPCContext) *Periodic { // Force is used to force a new instance of a periodic job func (p *Periodic) Force(args *structs.PeriodicForceRequest, reply *structs.PeriodicForceResponse) error { + + authErr := p.srv.Authenticate(p.ctx, args) if done, err := p.srv.forward("Periodic.Force", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "periodic", "force"}, time.Now()) // Check for write-job permissions - if aclObj, err := p.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := p.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityDispatchJob) && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) { return structs.ErrPermissionDenied diff --git a/nomad/scaling_endpoint.go b/nomad/scaling_endpoint.go index 95b021cdf..38ed53447 100644 --- a/nomad/scaling_endpoint.go +++ b/nomad/scaling_endpoint.go @@ -28,16 +28,20 @@ func NewScalingEndpoint(srv *Server, ctx *RPCContext) *Scaling { // ListPolicies is used to list the policies func (p *Scaling) ListPolicies(args *structs.ScalingPolicyListRequest, reply *structs.ScalingPolicyListResponse) error { + authErr := p.srv.Authenticate(p.ctx, args) if done, err := p.srv.forward("Scaling.ListPolicies", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "scaling", "list_policies"}, time.Now()) if args.RequestNamespace() == structs.AllNamespacesSentinel { return p.listAllNamespaces(args, reply) } - if aclObj, err := p.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := p.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil { hasListScalingPolicies := aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityListScalingPolicies) @@ -95,13 +99,17 @@ func (p *Scaling) ListPolicies(args *structs.ScalingPolicyListRequest, reply *st func (p *Scaling) GetPolicy(args *structs.ScalingPolicySpecificRequest, reply *structs.SingleScalingPolicyResponse) error { + authErr := p.srv.Authenticate(p.ctx, args) if done, err := p.srv.forward("Scaling.GetPolicy", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "scaling", "get_policy"}, time.Now()) // Check for list-job permissions - if aclObj, err := p.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := p.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil { hasReadScalingPolicy := aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadScalingPolicy) @@ -144,7 +152,7 @@ func (p *Scaling) GetPolicy(args *structs.ScalingPolicySpecificRequest, func (p *Scaling) listAllNamespaces(args *structs.ScalingPolicyListRequest, reply *structs.ScalingPolicyListResponse) error { // Check for list-job permissions - aclObj, err := p.srv.ResolveToken(args.AuthToken) + aclObj, err := p.srv.ResolveACL(args) if err != nil { return err } diff --git a/nomad/search_endpoint.go b/nomad/search_endpoint.go index a7c45e6f7..b79935477 100644 --- a/nomad/search_endpoint.go +++ b/nomad/search_endpoint.go @@ -548,12 +548,17 @@ func (*Search) silenceError(err error) bool { // PrefixSearch is used to list matches for a given prefix, and returns // matching jobs, evaluations, allocations, and/or nodes. func (s *Search) PrefixSearch(args *structs.SearchRequest, reply *structs.SearchResponse) error { + + authErr := s.srv.Authenticate(s.ctx, args) if done, err := s.srv.forward("Search.PrefixSearch", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "search", "prefix_search"}, time.Now()) - aclObj, err := s.srv.ResolveToken(args.AuthToken) + aclObj, err := s.srv.ResolveACL(args) if err != nil { return err } @@ -676,12 +681,17 @@ func sufficientSearchPerms(aclObj *acl.ACL, namespace string, context structs.Co // // The results are in descending order starting with strongest match, per Context type. func (s *Search) FuzzySearch(args *structs.FuzzySearchRequest, reply *structs.FuzzySearchResponse) error { + + authErr := s.srv.Authenticate(s.ctx, args) if done, err := s.srv.forward("Search.FuzzySearch", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "search", "fuzzy_search"}, time.Now()) - aclObj, err := s.srv.ResolveToken(args.AuthToken) + aclObj, err := s.srv.ResolveACL(args) if err != nil { return err } diff --git a/nomad/service_registration_endpoint.go b/nomad/service_registration_endpoint.go index 61d92890b..5ab47854c 100644 --- a/nomad/service_registration_endpoint.go +++ b/nomad/service_registration_endpoint.go @@ -14,7 +14,6 @@ import ( "github.com/hashicorp/go-set" "github.com/hashicorp/nomad/acl" - "github.com/hashicorp/nomad/helper" "github.com/hashicorp/nomad/nomad/state" "github.com/hashicorp/nomad/nomad/state/paginator" "github.com/hashicorp/nomad/nomad/structs" @@ -105,9 +104,13 @@ func (s *ServiceRegistration) DeleteByID( args *structs.ServiceRegistrationDeleteByIDRequest, reply *structs.ServiceRegistrationDeleteByIDResponse) error { + authErr := s.srv.Authenticate(s.ctx, args) if done, err := s.srv.forward(structs.ServiceRegistrationDeleteByIDRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "service_registration", "delete_id"}, time.Now()) // Nomad service registrations can only be used once all servers, in the @@ -118,7 +121,7 @@ func (s *ServiceRegistration) DeleteByID( } // Perform the ACL token resolution. - aclObj, err := s.srv.ResolveToken(args.AuthToken) + aclObj, err := s.srv.ResolveACL(args) switch err { case nil: @@ -198,9 +201,13 @@ func (s *ServiceRegistration) List( args *structs.ServiceRegistrationListRequest, reply *structs.ServiceRegistrationListResponse) error { + authErr := s.srv.Authenticate(s.ctx, args) if done, err := s.srv.forward(structs.ServiceRegistrationListRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "service_registration", "list"}, time.Now()) // If the caller has requested to list services across all namespaces, use @@ -209,9 +216,12 @@ func (s *ServiceRegistration) List( return s.listAllServiceRegistrations(args, reply) } - // Perform our mixed auth handling. - if err := s.handleMixedAuthEndpoint(args.QueryOptions, acl.NamespaceCapabilityReadJob); err != nil { - return err + aclObj, err := s.srv.ResolveClientOrACL(args) + if err != nil { + return structs.ErrPermissionDenied + } + if !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { + return structs.ErrPermissionDenied } // Set up and return the blocking query. @@ -270,9 +280,9 @@ func (s *ServiceRegistration) listAllServiceRegistrations( args *structs.ServiceRegistrationListRequest, reply *structs.ServiceRegistrationListResponse) error { - // Perform token resolution. The request already goes through forwarding + // Perform ACL resolution. The request already goes through forwarding // and metrics setup before being called. - aclObj, err := s.srv.ResolveToken(args.AuthToken) + aclObj, err := s.srv.ResolveACL(args) if err != nil { return err } @@ -360,14 +370,21 @@ func (s *ServiceRegistration) GetService( args *structs.ServiceRegistrationByNameRequest, reply *structs.ServiceRegistrationByNameResponse) error { + authErr := s.srv.Authenticate(s.ctx, args) if done, err := s.srv.forward(structs.ServiceRegistrationGetServiceRPCMethod, args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } defer metrics.MeasureSince([]string{"nomad", "service_registration", "get_service"}, time.Now()) - // Perform our mixed auth handling. - if err := s.handleMixedAuthEndpoint(args.QueryOptions, acl.NamespaceCapabilityReadJob); err != nil { - return err + aclObj, err := s.srv.ResolveClientOrACL(args) + if err != nil { + return structs.ErrPermissionDenied + } + if !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { + return structs.ErrPermissionDenied } // Set up the blocking query. @@ -498,64 +515,3 @@ func (*ServiceRegistration) choose(services []*structs.ServiceRegistration, para return chosen, nil } - -// handleMixedAuthEndpoint is a helper to handle auth on RPC endpoints that can -// either be called by Nomad nodes, or by external clients. -func (s *ServiceRegistration) handleMixedAuthEndpoint(args structs.QueryOptions, cap string) error { - - // Perform the initial token resolution. - aclObj, err := s.srv.ResolveToken(args.AuthToken) - - switch err { - case nil: - // Perform our ACL validation. If the object is nil, this means ACLs - // are not enabled, otherwise trigger the allowed namespace function. - if aclObj != nil { - if !aclObj.AllowNsOp(args.RequestNamespace(), cap) { - return structs.ErrPermissionDenied - } - } - default: - // Attempt to verify the token as a JWT with a workload - // identity claim if it's not a secret ID. - // COMPAT(1.4.0): we can remove this conditional in 1.5.0 - if !helper.IsUUID(args.AuthToken) { - claims, err := s.srv.VerifyClaim(args.AuthToken) - if err != nil { - return err - } - if claims == nil { - return structs.ErrPermissionDenied - } - return nil - } - - // COMPAT(1.4.0): Nomad 1.3.0 shipped with authentication by - // node secret but that's been replaced with workload identity - // in 1.4.0. Leave this here for backwards compatibility - // between clients and servers during cluster upgrades, but - // remove for 1.5.0 - - // In the event we got any error other than ErrTokenNotFound, consider this - // terminal. - if err != structs.ErrTokenNotFound { - return err - } - - // Attempt to lookup AuthToken as a Node.SecretID and - // return any error wrapped along with the original. - node, stateErr := s.srv.fsm.State().NodeBySecretID(nil, args.AuthToken) - if stateErr != nil { - var mErr multierror.Error - mErr.Errors = append(mErr.Errors, err, stateErr) - return mErr.ErrorOrNil() - } - // At this point, we do not have a valid ACL token, nor are we being - // called, or able to confirm via the state store, by a node. - if node == nil { - return structs.ErrTokenNotFound - } - - } - return nil -} diff --git a/nomad/status_endpoint.go b/nomad/status_endpoint.go index 4ac6f238e..ff5653bc7 100644 --- a/nomad/status_endpoint.go +++ b/nomad/status_endpoint.go @@ -70,8 +70,12 @@ func (s *Status) Peers(args *structs.GenericRequest, reply *[]string) error { // Members return the list of servers in a cluster that a particular server is // aware of func (s *Status) Members(args *structs.GenericRequest, reply *structs.ServerMembersResponse) error { + authErr := s.srv.Authenticate(s.ctx, args) + if authErr != nil { + return structs.ErrPermissionDenied + } // Check node read permissions - if aclObj, err := s.srv.ResolveToken(args.AuthToken); err != nil { + if aclObj, err := s.srv.ResolveACL(args); err != nil { return err } else if aclObj != nil && !aclObj.AllowNodeRead() { return structs.ErrPermissionDenied diff --git a/nomad/system_endpoint.go b/nomad/system_endpoint.go index 87deeac67..de17b1035 100644 --- a/nomad/system_endpoint.go +++ b/nomad/system_endpoint.go @@ -22,12 +22,17 @@ func NewSystemEndpoint(srv *Server, ctx *RPCContext) *System { // GarbageCollect is used to trigger the system to immediately garbage collect nodes, evals // and jobs. func (s *System) GarbageCollect(args *structs.GenericRequest, reply *structs.GenericResponse) error { + + authErr := s.srv.Authenticate(s.ctx, args) if done, err := s.srv.forward("System.GarbageCollect", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } // Check management level permissions - if acl, err := s.srv.ResolveToken(args.AuthToken); err != nil { + if acl, err := s.srv.ResolveACL(args); err != nil { return err } else if acl != nil && !acl.IsManagement() { return structs.ErrPermissionDenied @@ -46,12 +51,17 @@ func (s *System) GarbageCollect(args *structs.GenericRequest, reply *structs.Gen // ReconcileJobSummaries reconciles the summaries of all the jobs in the state // store func (s *System) ReconcileJobSummaries(args *structs.GenericRequest, reply *structs.GenericResponse) error { + + authErr := s.srv.Authenticate(s.ctx, args) if done, err := s.srv.forward("System.ReconcileJobSummaries", args, args, reply); done { return err } + if authErr != nil { + return structs.ErrPermissionDenied + } // Check management level permissions - if acl, err := s.srv.ResolveToken(args.AuthToken); err != nil { + if acl, err := s.srv.ResolveACL(args); err != nil { return err } else if acl != nil && !acl.IsManagement() { return structs.ErrPermissionDenied diff --git a/nomad/variables_endpoint.go b/nomad/variables_endpoint.go index af5987fa5..c5f2da950 100644 --- a/nomad/variables_endpoint.go +++ b/nomad/variables_endpoint.go @@ -40,7 +40,7 @@ func (sv *Variables) Apply(args *structs.VariablesApplyRequest, reply *structs.V return err } if authErr != nil { - return authErr + return structs.ErrPermissionDenied } defer metrics.MeasureSince([]string{ @@ -114,8 +114,8 @@ func svePreApply(sv *Variables, args *structs.VariablesApplyRequest, vd *structs canRead = false var aclObj *acl.ACL - // Perform the ACL token resolution. - if aclObj, err = sv.srv.ResolveToken(args.AuthToken); err != nil { + // Perform the ACL resolution. + if aclObj, err = sv.srv.ResolveACL(args); err != nil { return } else if aclObj != nil { hasPerm := func(perm string) bool { @@ -230,7 +230,7 @@ func (sv *Variables) Read(args *structs.VariablesReadRequest, reply *structs.Var return err } if authErr != nil { - return authErr + return structs.ErrPermissionDenied } defer metrics.MeasureSince([]string{"nomad", "variables", "read"}, time.Now()) @@ -280,7 +280,7 @@ func (sv *Variables) List( return err } if authErr != nil { - return authErr + return structs.ErrPermissionDenied } defer metrics.MeasureSince([]string{"nomad", "variables", "list"}, time.Now()) @@ -295,7 +295,7 @@ func (sv *Variables) List( var err error aclToken := args.GetIdentity().GetACLToken() if aclToken != nil { - aclObj, err = sv.srv.ResolveACL(args.GetIdentity().GetACLToken()) + aclObj, err = sv.srv.ResolveACLForToken(aclToken) if err != nil { return err } @@ -385,7 +385,7 @@ func (sv *Variables) listAllVariables( var err error aclToken := args.GetIdentity().GetACLToken() if aclToken != nil { - aclObj, err = sv.srv.ResolveACL(args.GetIdentity().GetACLToken()) + aclObj, err = sv.srv.ResolveACLForToken(aclToken) if err != nil { return err } @@ -499,7 +499,7 @@ func (sv *Variables) handleMixedAuthEndpoint(args structs.QueryOptions, cap, pat var err error aclToken := args.GetIdentity().GetACLToken() if aclToken != nil { - aclObj, err = sv.srv.ResolveACL(args.GetIdentity().GetACLToken()) + aclObj, err = sv.srv.ResolveACLForToken(aclToken) if err != nil { return nil, nil, err }