From 87fe203aeac71ee165396c6e20a685647430e972 Mon Sep 17 00:00:00 2001 From: vky5 Date: Thu, 11 Jun 2026 14:30:32 +0530 Subject: [PATCH 1/2] auth: allow nodes to sync their own allocations during node pool changes --- nomad/acl.go | 3 ++- nomad/alloc_endpoint.go | 6 +++--- nomad/auth/auth.go | 5 +++++ nomad/auth/auth_test.go | 27 +++++++++++++++++++++++++-- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/nomad/acl.go b/nomad/acl.go index 2e57ebfb17d..bac582adb18 100644 --- a/nomad/acl.go +++ b/nomad/acl.go @@ -29,11 +29,12 @@ func (s *Server) ResolveAuthorizedClientNodePoolByNodeID(aclObj *acl.ACL, nodeID } func (s *Server) AuthorizeClientAllocation( + identity *structs.AuthenticatedIdentity, aclObj *acl.ACL, alloc *structs.Allocation, allowNsOp func(*acl.ACL, string) bool, ) error { - return s.auth.AuthorizeClientAllocation(aclObj, alloc, allowNsOp) + return s.auth.AuthorizeClientAllocation(identity, aclObj, alloc, allowNsOp) } func (s *Server) ResolveAuthorizedClientNodePoolByServiceRegistrationID( diff --git a/nomad/alloc_endpoint.go b/nomad/alloc_endpoint.go index 584ed6ac338..c7d928d0abd 100644 --- a/nomad/alloc_endpoint.go +++ b/nomad/alloc_endpoint.go @@ -165,7 +165,7 @@ func (a *Alloc) GetAlloc(args *structs.AllocSpecificRequest, reply.Alloc = out // Re-check namespace in case it differs from request. - if err := a.srv.AuthorizeClientAllocation(aclObj, out, allowNsOp); err != nil { + if err := a.srv.AuthorizeClientAllocation(args.GetIdentity(), aclObj, out, allowNsOp); err != nil { return structs.NewErrUnknownAllocation(args.AllocID) } @@ -231,7 +231,7 @@ func (a *Alloc) GetAllocs(args *structs.AllocsGetRequest, continue } - if err := a.srv.AuthorizeClientAllocation(aclObj, out, nil); err != nil { + if err := a.srv.AuthorizeClientAllocation(args.GetIdentity(), aclObj, out, nil); err != nil { return err } @@ -492,7 +492,7 @@ func (a *Alloc) SignIdentities(args *structs.AllocIdentitiesRequest, reply *stru continue } - if err := a.srv.AuthorizeClientAllocation(aclObj, out, nil); err != nil { + if err := a.srv.AuthorizeClientAllocation(args.GetIdentity(), aclObj, out, nil); err != nil { return err } diff --git a/nomad/auth/auth.go b/nomad/auth/auth.go index a0ffd888676..01523579c4f 100644 --- a/nomad/auth/auth.go +++ b/nomad/auth/auth.go @@ -665,6 +665,7 @@ func (s *Authenticator) ResolveAuthorizedClientNodePoolByNodeID(aclObj *acl.ACL, // fall back to namespace-based authorization when client-scoped authorization // does not apply. func (s *Authenticator) AuthorizeClientAllocation( + identity *structs.AuthenticatedIdentity, aclObj *acl.ACL, alloc *structs.Allocation, allowNsOp func(*acl.ACL, string) bool, @@ -673,6 +674,10 @@ func (s *Authenticator) AuthorizeClientAllocation( return structs.ErrPermissionDenied } + if identity != nil && AuthorizeSameNode(identity, alloc.NodeID) == nil { + return nil + } + if aclObj.AllowClientOp(alloc.Job.NodePool) { return nil } diff --git a/nomad/auth/auth_test.go b/nomad/auth/auth_test.go index eaaa9864927..0f901eb5758 100644 --- a/nomad/auth/auth_test.go +++ b/nomad/auth/auth_test.go @@ -1713,14 +1713,37 @@ func TestResolveAuthorizedClientNodePoolHelpers(t *testing.T) { t.Run("authorize client allocation", func(t *testing.T) { alloc := mock.Alloc() alloc.Job.NodePool = node.NodePool + alloc.NodeID = node.ID - must.NoError(t, auth.AuthorizeClientAllocation(aclObj, alloc, nil)) - must.ErrorIs(t, auth.AuthorizeClientAllocation(acl.NewClientACL("other-pool"), alloc, nil), structs.ErrPermissionDenied) + must.NoError(t, auth.AuthorizeClientAllocation(nil, aclObj, alloc, nil)) + must.ErrorIs(t, auth.AuthorizeClientAllocation(nil, acl.NewClientACL("other-pool"), alloc, nil), structs.ErrPermissionDenied) must.NoError(t, auth.AuthorizeClientAllocation( + nil, acl.NewClientACL("other-pool"), alloc, func(_ *acl.ACL, ns string) bool { return ns == alloc.Namespace }, )) + + // Test identity-based authorization when node pool mismatches + matchingIdentity := &structs.AuthenticatedIdentity{ + ClientID: node.ID, + } + must.NoError(t, auth.AuthorizeClientAllocation( + matchingIdentity, + acl.NewClientACL("other-pool"), + alloc, + nil, + )) + + mismatchIdentity := &structs.AuthenticatedIdentity{ + ClientID: "other-node", + } + must.ErrorIs(t, auth.AuthorizeClientAllocation( + mismatchIdentity, + acl.NewClientACL("other-pool"), + alloc, + nil, + ), structs.ErrPermissionDenied) }) t.Run("resolve by service registration id", func(t *testing.T) { From 20d4fb75b7889bc8cb14c51fcc7afcd6046a457b Mon Sep 17 00:00:00 2001 From: vky5 Date: Thu, 11 Jun 2026 17:38:35 +0530 Subject: [PATCH 2/2] ci: update semgrep rule for new AuthorizeClientAllocation signature --- .semgrep/rpc_endpoint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.semgrep/rpc_endpoint.yml b/.semgrep/rpc_endpoint.yml index b1d65b1d6ef..1f4c85a5c2f 100644 --- a/.semgrep/rpc_endpoint.yml +++ b/.semgrep/rpc_endpoint.yml @@ -157,7 +157,7 @@ rules: return structs.ErrPermissionDenied } ... - if err := $A.$B.AuthorizeClientAllocation(aclObj, ..., ...); err != nil { + if err := $A.$B.AuthorizeClientAllocation(..., aclObj, ..., ...); err != nil { return err } ...