diff --git a/CHANGELOG.md b/CHANGELOG.md index 318f2f9f3f66..54e6d7e3cb24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ FEATURES: IMPROVEMENTS: * core: Improved job deregistration error logging. [[GH-8745](https://github.com/hashicorp/nomad/issues/8745)] + * acl: Allow operators with `namespace:dispatch-job` capability to force periodic job invocation [[GH-9205](https://github.com/hashicorp/nomad/issues/9205)] * api: Added support for cancellation contexts to HTTP API. [[GH-8836](https://github.com/hashicorp/nomad/issues/8836)] * api: Job Register API now permits non-zero initial Version to accommodate multi-region deployments. [[GH-9071](https://github.com/hashicorp/nomad/issues/9071)] * api: Added ?resources=true query parameter to /v1/nodes and /v1/allocations to include resource allocations in listings. [[GH-9055](https://github.com/hashicorp/nomad/issues/9055)] diff --git a/nomad/periodic_endpoint.go b/nomad/periodic_endpoint.go index 4864fdd68322..b8e4807cf2b4 100644 --- a/nomad/periodic_endpoint.go +++ b/nomad/periodic_endpoint.go @@ -28,7 +28,7 @@ func (p *Periodic) Force(args *structs.PeriodicForceRequest, reply *structs.Peri // Check for write-job permissions if aclObj, err := p.srv.ResolveToken(args.AuthToken); err != nil { return err - } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) { + } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityDispatchJob) && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) { return structs.ErrPermissionDenied } diff --git a/nomad/periodic_endpoint_test.go b/nomad/periodic_endpoint_test.go index b4dd8ec26e54..5f2a5ed8e9e3 100644 --- a/nomad/periodic_endpoint_test.go +++ b/nomad/periodic_endpoint_test.go @@ -127,6 +127,24 @@ func TestPeriodicEndpoint_Force_ACL(t *testing.T) { } } + // Fetch the response with a valid token having dispatch permission + { + policy := mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityDispatchJob}) + token := mock.CreatePolicyAndToken(t, state, 1005, "valid", policy) + req.AuthToken = token.SecretID + var resp structs.PeriodicForceResponse + assert.Nil(msgpackrpc.CallWithCodec(codec, "Periodic.Force", req, &resp)) + assert.NotEqual(uint64(0), resp.Index) + + // Lookup the evaluation + ws := memdb.NewWatchSet() + eval, err := state.EvalByID(ws, resp.EvalID) + assert.Nil(err) + if assert.NotNil(eval) { + assert.Equal(eval.CreateIndex, resp.EvalCreateIndex) + } + } + // Fetch the response with management token { req.AuthToken = root.SecretID diff --git a/website/pages/api-docs/jobs.mdx b/website/pages/api-docs/jobs.mdx index be85fdb565ba..1b64afd973b8 100644 --- a/website/pages/api-docs/jobs.mdx +++ b/website/pages/api-docs/jobs.mdx @@ -1666,9 +1666,9 @@ The table below shows this endpoint's support for [blocking queries](/api-docs#blocking-queries) and [required ACLs](/api-docs#acls). -| Blocking Queries | ACL Required | -| ---------------- | ---------------------- | -| `NO` | `namespace:submit-job` | +| Blocking Queries | ACL Required | +| ---------------- | -------------------------------------------------- | +| `NO` | `namespace:dispatch-job` or `namespace:submit-job` | ### Parameters