Conversation
Resolves the open question that blocked this phase. Inspecting ovadm::add_compiler and its configure_compiler_ssl task confirms every compiler already holds a certificate signed by the primary's CA, its private key, the CA certificate and a CRL at Puppet's standard paths. codavox reuses them and issues nothing: no second PKI to provision, distribute or rotate, and revoking a compiler's Puppet certificate revokes its access to code as a side effect. Authorization needs more than CA membership. A certificate signed by the Puppet CA proves only that the peer is some enrolled node, and every agent in the estate clears that bar, while Puppet manifests routinely reference internal hostnames, credential paths and topology. The publisher therefore requires a pp_role extension matching --allow-role. ovadm already writes pp_role: openvox_compiler into csr_attributes.yaml, so no extra configuration is needed. The OID 1.3.6.1.4.1.34380.1.1.13 was verified against openvox lib/puppet/ssl/oids.rb rather than taken from memory. The role check runs in VerifyConnection, not VerifyPeerCertificate. gosec flagged that the latter is skipped entirely on resumed sessions, so a peer that completed one handshake could keep reconnecting without the role ever being rechecked. ServerTLS also refuses to build a configuration with no allowed roles, so the constraint cannot be omitted by accident. Sealing happens on Reseal rather than per request: it walks and hashes a whole environment, and doing it per request would let two compilers polling either side of an r10k run observe different ids for one deploy. The version map is served no-store because polling is the correctness mechanism; artifacts are served immutable because they are content-addressed by the code_id in the URL. Only the current code_id is servable, since compilers retain old versions themselves for in-flight runs. Verified by an end-to-end test that builds the real binary, lays out SSL material the way ovadm leaves it, and checks over an actual TLS connection that a compiler is admitted and an ordinary agent is refused. Signed-off-by: Michael Harp <mike@mikeharp.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Completes the serving half of phase 2, and resolves the open question that was blocking it.
The blocker is resolved: no second PKI
I inspected
ovadm::add_compilerand itsconfigure_compiler_ssltask. Every compiler already ends up with, at Puppet's standard paths:codavox reuses them and issues nothing. Nothing to provision, distribute, or rotate — and revoking a compiler's Puppet certificate revokes its access to code as a side effect, which is what an operator would expect.
CA membership is not authorization
A certificate signed by the Puppet CA proves only that the peer is some enrolled node. Every agent in the estate clears that bar, and Puppet manifests routinely reference internal hostnames, credential paths, and topology — a compromised leaf node should not be able to read the whole estate's code.
The publisher additionally requires a
pp_roleextension matching--allow-role.ovadmalready writespp_role: openvox_compilerintocsr_attributes.yaml, so compilers carry their role with no extra configuration.The OID
1.3.6.1.4.1.34380.1.1.13was verified against openvoxlib/puppet/ssl/oids.rbrather than taken from memory.A security fix gosec caught
The role check initially used
VerifyPeerCertificate. gosec flagged that it is skipped entirely on resumed TLS sessions — a peer that completed one handshake could keep reconnecting without the role ever being rechecked.Moved to
VerifyConnection, which runs on resumed connections too.ServerTLSnow also refuses to build a configuration with no allowed roles, so the constraint cannot be omitted by accident rather than being something a caller bolts on.Verified end to end, not just in-process
TestPublishBinaryEndToEndbuilds the real binary, lays out SSL material the way ovadm leaves it on a node, starts the publisher, and checks over an actual TLS connection:That covers argument parsing, certificate discovery from certname and ssldir, and the TLS wiring — none of which an in-process handler test would reach.
Design decisions worth noting
Sealing happens on
Reseal, not per request. It walks and hashes a whole environment; doing it per request would also let two compilers polling either side of an r10k run observe different ids for one deploy.The version map is
no-store, because polling is the correctness mechanism and a cached response would pin a compiler to a stale version. Artifacts areimmutable, because they are content-addressed by thecode_idin the URL.Only the current
code_idis servable. Serving arbitrary history would mean keeping every past tree on the publisher; compilers retain old versions themselves, which is what in-flight agent runs actually need.codavox stages nothing — it reads a directory r10k already populated, keeping the trust boundary small.
Known gap
The publisher seals once at startup. Triggering a reseal after each r10k run is the operator's job for now; a watch mode is planned and noted in the docs.
🤖 Generated with Claude Code