Allow commands to be bound to the submitter's certificate - #458
Conversation
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>
There was a problem hiding this comment.
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-allowlistfile. - 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.
austb
left a comment
There was a problem hiding this comment.
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")] |
There was a problem hiding this comment.
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>
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