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 }