Skip to content

Allow commands to be bound to the submitter's certificate - #458

Open
silug wants to merge 2 commits into
OpenVoxProject:mainfrom
silug:bind-commands-to-submitter
Open

Allow commands to be bound to the submitter's certificate#458
silug wants to merge 2 commits into
OpenVoxProject:mainfrom
silug:bind-commands-to-submitter

Conversation

@silug

@silug silug commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Pull Request (PR) description

A command names the node it applies to in its certname parameter, and nothing tied that parameter to the certificate the command was submitted with. Any client permitted to reach /pdb/cmd could therefore replace the facts, catalog, or reports of any node in the fleet, or deactivate it. Forged facts are the sharpest edge of that, since they feed catalog compilation for the node they name.

Submitting for another node is a legitimate need: OpenVox Server submits commands for every agent whose catalog it compiles. So rather than tie every command to its submitter, the new [puppetdb] trusted-submitter-allowlist names the certnames that may do it. Where it is set, any other client may only submit commands for its own certname.

The setting is optional and unset by default, so existing deployments keep working until an operator opts in. The check applies to both command formats, since it runs after request normalization, and it does not constrain requests that presented no certificate at all, which have no name to compare against.

Generated by Claude Code

This Pull Request (PR) fixes the following issues

N/A

A command names the node it applies to in its certname parameter, and
nothing tied that parameter to the certificate the command was submitted
with. Any client permitted to reach /pdb/cmd could therefore replace the
facts, catalog, or reports of any node in the fleet, or deactivate it.
Forged facts are the sharpest edge of that, since they feed catalog
compilation for the node they name.

Submitting for another node is a legitimate need: OpenVox Server submits
commands for every agent whose catalog it compiles. So rather than tie
every command to its submitter, the new [puppetdb]
trusted-submitter-allowlist names the certnames that may do it. Where it
is set, any other client may only submit commands for its own certname.

The setting is optional and unset by default, so existing deployments
keep working until an operator opts in. The check applies to both
command formats, since it runs after request normalization, and it does
not constrain requests that presented no certificate at all, which have
no name to compare against.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Steven Pritchard <steven.pritchard@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an optional authorization control for the /pdb/cmd command submission endpoint to ensure that, when configured, a client certificate’s CN can only submit commands for its own certname unless the submitter is explicitly allowlisted. This mitigates cross-node command spoofing (notably forged facts) while still supporting legitimate “submit on behalf of” workflows (e.g., OpenVox Server).

Changes:

  • Adds submitter-vs-certname authorization middleware to the command HTTP app, backed by a trusted-submitter-allowlist file.
  • Wires the new config setting through routing/config and documents it (plus sample config).
  • Adds tests covering allowlisted, non-allowlisted, and no-certificate scenarios, including old-format (payload-only) command submissions.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/puppetlabs/puppetdb/http/command.clj Implements allowlist-backed submitter authorization and wires it into the command middleware stack.
src/puppetlabs/puppetdb/pdb_routing.clj Passes trusted-submitter-allowlist from config into the command app.
src/puppetlabs/puppetdb/config.clj Adds schema + accessor for :trusted-submitter-allowlist.
test/puppetlabs/puppetdb/testutils.clj Extends the command-app test fixture to accept an optional allowlist path.
test/puppetlabs/puppetdb/http/command_test.clj Adds test coverage for the new submitter authorization behavior.
documentation/configure.markdown Documents the new trusted-submitter-allowlist setting and its behavior.
config.sample.ini Adds a commented example for configuring trusted-submitter-allowlist.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@silug
silug marked this pull request as ready for review August 4, 2026 19:59

@austb austb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the optional allowlist is a feasible feature for the majority of folks who are not submitting commands from agentless nodes. But I don't think the certname parameter is safe to use as a security check because it may not match the underlying data.

And since OpenVoxDB streams the command data straight to disk for maximum performance. We should not read the POST data at the point of submission because that holds on to the jruby thread in openvox-server for too long.

[handler submits-for-other-nodes?]
(fn authorize-submitter
[{:keys [params ssl-client-cn] :as request}]
(let [certname (params "certname")]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that OpenVoxDB can use this certname for its certificate check. The actual data stored in the DB is what's in the command's POST body, and there's no requirement that the certname parameter match the certname in the body. So a malicious node could just continue to send their own certname in the parameter while changing the certname in the data to overwrite all the rest of the nodes data.

The certname parameter that the command endpoint checks against the
submitter's certificate only names the queue entry. What actually gets
stored is the certname inside the command's payload, and nothing
required the two to agree, so a client could submit under its own
certname while naming any other node in the payload. The submitter check
constrained the label rather than the data.

Compare the two once the payload has been normalized to the latest wire
format, and treat a disagreement as a fatal error so the command lands
in the DLO rather than being retried. Normalization has to come first,
because the older facts formats name the certname differently and
deactivate node does not carry a map at all.

The comparison deliberately happens at processing time rather than at
submission. OpenVoxDB streams command bodies to disk without reading
them, and parsing the payload during the request would hold an
OpenVox Server jruby thread for the length of the read. The cost is that
rejection is asynchronous: a forged command still gets its 200 and UUID.

A cmdref's certname is really a certid, which may be a sanitized and
truncated proxy for the original, so certname-matches-cmdref? puts the
candidate through the same transformation instead of comparing directly.

Like the endpoint check, this only applies where trusted-submitter-allowlist
is set, so deployments that have not opted in are unaffected.

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Steven Pritchard <steven.pritchard@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants