Repository navigation
feat(sponsord_onramp): deploy sponsord-onramp behind Caddy beside sponsord - #87
Merged
Merged
Conversation
baseline_extra_inbound came from playbooks/group_vars/decdn_nodes.yml and read decdn_bind_port, a decdn_node role default. On a host in both decdn_nodes and sponsord_hosts, the sponsord play runs baseline without that role, so the nftables render hit an undefined variable. all.yml now builds the list of public holes from the host's groups (_baseline_service_inbound, with the 4433 fallback), and decdn_nodes.yml and the new sponsord_hosts.yml both point baseline_extra_inbound at it. Every play on a host renders the same firewall. tests/firewall-holes pins the result per host shape and runs in `make test-scripts`; CI's scripts job installs ansible-core for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nsord Upstream decdn/sponsord ships two server-side services; only the daemon was deployed. The new sponsord_onramp role deploys the public onramp (Turnstile gate, decdn.sh/decdn.ps1 installers, the decdn-sponsored CLI API) as a second play of playbooks/sponsord.yml on sponsord_onramp_hosts, which must also be in sponsord_hosts: it reads the daemon's own API token and calls it on loopback. - Install, unit, gates: the sponsord role's patterns. Manual binary or a GPG-verified sponsord-onramp-v* release (KEYS from the sponsord role), DynamicUser with the token and the host-provisioned Turnstile secret as LoadCredential=, PartOf=sponsord.service, a restart-inputs record, and a fatal /healthz gate on a loopback listener. - Public inputs: the RPC URL is served to every user, so preflight refuses userinfo, a query and anything upstream's installers cannot quote. Release pins and the sitekey follow upstream's shapes. CapacityBond/SlashJudge come from a third generated network mirror. - TLS front: Caddy from the distro by default (role-owned, validated Caddyfile, admin API off, ACME or tls internal; a foreign Caddyfile is refused without an explicit overwrite), or proxy "none". tcp/80 + 443 come from _baseline_service_inbound. - Alloy: grafana_alloy_sponsord_onramp_enabled labels and re-levels the onramp's journal stream (no /metrics, no scrape); the systemd collector also watches sponsord-onramp.service and caddy.service. - Tests: molecule/sponsord-onramp (Debian 12 + Ubuntu 24.04, real Caddy with tls internal: https chain, a forged X-Forwarded-For, Turnstile rotation, fatal gate, foreign Caddyfile), 25 validation cases, a new lint-alloy render + level-harness case, the firewall-holes shapes. - The role ships in the decdn.node collection (now five roles). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- tests/firewall-holes: write each host's list with the core copy module.
The json stdout callback lives in ansible.posix, so the check failed on
a core-only install such as CI's. It also pins all.yml's fallbacks
(4433, "caddy") to the role defaults.
- Caddy probes: a plain-http probe must get Caddy's 308 redirect (fatal,
no certificate needed). With ACME, only a not-yet-issued certificate
(curl 35/60) warns; a 502, nothing on 443 or a hang now fails the run.
HTTP/3 is off, since udp/443 is not opened.
- The foreign-Caddyfile guard keys on the file, not the dpkg package: a
Caddyfile without the marker is refused unless it is the package's
untouched default (dpkg's conffile md5) or the overwrite is confirmed.
An unreadable file is an error, not "no marker".
- Drop the hard-coded sponsord_hosts group check (it broke the role for
collection users); the token gate and /healthz already prove the daemon
is there. Cross-check the onramp profile's chain id against an explicit
sponsord_chain_id. Derive the onramp's files from sponsord_onramp_etc,
and make the Caddyfile path a role var. Tighten the ACME email check,
report an unreadable Turnstile secret as such, and warn while a
role-owned Caddy is left behind under proxy "none".
- Tests:
- The stubs now use the daemon token (an authenticated /v1/info) and
require ONRAMP_BIND/ONRAMP_DAEMON_URL.
- verify asserts the exact X-Forwarded-For and contract addresses.
- side_effect covers the no-op re-run, the record surviving a failed
run, the missing-record restart, PartOf recovery, the gate page,
proxy "none", ACME mode, a dead upstream behind Caddy and the
package-default Caddyfile.
- Seven more validation cases.
- Unique Alloy harness markers.
- Docs: hard rule 2 and the firewall notes name the Caddy holes, the
role README says the playbooks (not the role) open them, Ubuntu needs
universe, and the wording fixes from the comment review.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Group membership is not enforced, and Caddy can start with its package-default configuration before validation.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Adds the sponsord_onramp Ansible role, deploying the public sponsorship gateway behind Caddy alongside sponsord.
Changes:
- Adds hardened onramp installation, configuration, validation, TLS proxying, and health gates.
- Derives shared firewall rules and extends Alloy journal processing.
- Adds Molecule, validation, firewall, packaging, CI, and documentation coverage.
| File | Description |
|---|---|
tests/scripts-test.sh |
Runs firewall resolution checks. |
scripts/sync-network-profiles.py |
Generates the onramp network subset. |
README.md |
Documents the public onramp. |
docs/requirements.md |
Adds platform and port requirements. |
ansible/tests/firewall-holes/inventory.yml |
Defines firewall host fixtures. |
ansible/tests/firewall-holes/expected.json |
Records expected firewall rules. |
ansible/tests/firewall-holes/check.sh |
Validates resolved firewall rules. |
ansible/tests/alloy-config/validate.sh |
Tests onramp log processing. |
ansible/tests/alloy-config/render.yml |
Adds an onramp Alloy render case. |
ansible/roles/sponsord/README.md |
Links the companion onramp role. |
ansible/roles/sponsord_onramp/vars/main/networks.yml |
Mirrors required contract addresses. |
ansible/roles/sponsord_onramp/vars/main/caddy.yml |
Defines the packaged Caddyfile path. |
ansible/roles/sponsord_onramp/vars/main/arch.yml |
Maps supported release architectures. |
ansible/roles/sponsord_onramp/templates/sponsord-onramp.service.j2 |
Defines the hardened systemd unit. |
ansible/roles/sponsord_onramp/templates/sponsord-onramp.env.j2 |
Renders public onramp settings. |
ansible/roles/sponsord_onramp/templates/Caddyfile.j2 |
Configures TLS reverse proxying. |
ansible/roles/sponsord_onramp/tasks/main.yml |
Implements provisioning and readiness gates. |
ansible/roles/sponsord_onramp/tasks/install.yml |
Implements manual and verified installs. |
ansible/roles/sponsord_onramp/tasks/caddy.yml |
Installs, configures, and probes Caddy. |
ansible/roles/sponsord_onramp/README.md |
Documents role operation and variables. |
ansible/roles/sponsord_onramp/meta/main.yml |
Adds Galaxy role metadata. |
ansible/roles/sponsord_onramp/handlers/main.yml |
Adds service restart handlers. |
ansible/roles/sponsord_onramp/defaults/main.yml |
Defines role defaults. |
ansible/roles/grafana_alloy/templates/config.alloy.j2 |
Labels and re-levels onramp logs. |
ansible/roles/grafana_alloy/tasks/preflight.yml |
Validates new Alloy settings. |
ansible/roles/grafana_alloy/README.md |
Documents onramp observability. |
ansible/roles/grafana_alloy/defaults/main.yml |
Adds onramp monitoring defaults. |
ansible/README.md |
Documents the fifth deployment role. |
ansible/playbooks/sponsord.yml |
Adds the onramp deployment play. |
ansible/playbooks/group_vars/sponsord_hosts.yml |
Applies shared firewall rules. |
ansible/playbooks/group_vars/decdn_nodes.yml |
Uses the shared firewall list. |
ansible/playbooks/group_vars/all.yml |
Derives firewall and Alloy settings. |
ansible/molecule/validation/converge.yml |
Adds negative input tests. |
ansible/molecule/sponsord/files/sponsord-stub |
Adds authenticated daemon information. |
ansible/molecule/sponsord-onramp/verify.yml |
Verifies the converged deployment. |
ansible/molecule/sponsord-onramp/side_effect.yml |
Tests rotation and failure recovery. |
ansible/molecule/sponsord-onramp/prepare.yml |
Stages scenario prerequisites. |
ansible/molecule/sponsord-onramp/molecule.yml |
Defines the new scenario. |
ansible/molecule/sponsord-onramp/files/sponsord-onramp-stub |
Provides the test onramp server. |
ansible/molecule/sponsord-onramp/files/gate.html |
Provides a custom gate fixture. |
ansible/molecule/sponsord-onramp/converge.yml |
Runs the production playbook. |
ansible/Makefile |
Documents the added scenario and role. |
ansible/inventory/hosts.yml.example |
Adds example onramp membership. |
ansible/inventory/group_vars/sponsord_onramp_hosts.yml |
Provides inventory configuration guidance. |
ansible/inventory/group_vars/decdn_nodes.yml |
Updates firewall guidance. |
ansible/galaxy/README.md |
Documents the collection’s fifth role. |
ansible/galaxy/galaxy.yml |
Updates collection metadata. |
ansible/galaxy/CHANGELOG.md |
Records the role and firewall fix. |
ansible/galaxy/build.sh |
Packages the new role. |
AGENTS.md |
Updates repository architecture guidance. |
.github/workflows/upstream-drift.yml |
Tracks the additional generated mirror. |
.github/workflows/molecule.yml |
Documents the expanded suite. |
.github/workflows/ci.yml |
Installs Ansible for firewall tests. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…n the playbook From the PR review: - Install Caddy with policy_rc_d 101, so the package's maintainer scripts never start it on its stock config while 80/443 are already open. A run that failed after the install could otherwise leave the default server public. Caddy first starts from the restart handler, after the managed Caddyfile has validated. - playbooks/sponsord.yml gains a fact-free first play that refuses an onramp host outside sponsord_hosts: such a host would skip baseline and get no Caddy firewall holes. The role still checks no group name, so collection users keep their own groups. tests/scripts-test.sh lifts that play out with yq and runs it against a fixture, both ways. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.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.


Summary
Upstream
decdn/sponsordships two server-side services. Ansible only deployed the daemon. This adds asponsord_onramprole forsponsord-onramp: the public Turnstile gate, thedecdn.sh/decdn.ps1installers, and the API thedecdn-sponsoredCLI polls. (decdn-sponsoreditself runs on users' machines, so it is not a deploy target.)playbooks/sponsord.ymlon a newsponsord_onramp_hostsgroup. Those hosts must also be insponsord_hosts, because the onramp uses the daemon's own API token and calls it on loopback.make deploy-sponsordruns both plays.sponsordrole's patterns.sponsord-onramp-v*release.DynamicUser, with the daemon token and a host-provisioned Turnstile secret asLoadCredential=credentials.PartOf=sponsord.service./healthzcheck on the loopback listener./etc/caddy/Caddyfile, runscaddy validatebefore it lands, and turns the admin API and HTTP/3 off.sponsord_onramp_proxy: nonelets you bring your own proxy.playbooks/group_vars/all.yml.grafana_alloy_sponsord_onramp_enabledlabels and re-levels the onramp's journal. There is no scrape, because the onramp has no/metrics.decdn.nodecollection, which now has five roles.There is also a separate fix commit,
5eb5012. On a host in bothdecdn_nodesandsponsord_hosts, the sponsord play's baseline could not render the node's udp/4433 rule, becausedecdn_bind_portis undefined outside thedecdn_noderole. The public holes are now built from a host's groups, so every play renders the same firewall.ansible/tests/firewall-holespins this per host shape and runs inmake test-scripts. CI'sscriptsjob now installs ansible-core.Testing
molecule/sponsord-onramp(Debian 12 and Ubuntu 24.04, stub binaries, the real distro Caddy):X-Forwarded-Foris replaced by the real client address;proxy: none;molecule/validation: 32 new negative cases.lint-alloy: a new render case, and a level-harness run against the real Alloy binary.make lint,lint-ansible,lint-alloy,test-scripts(the firewall check also in a core-only venv),security(KICS, 0 HIGH),galaxy-check, plus the molecule scenariossponsord-onramp,validation,sponsordandgrafana-cloud.Notes
sponsord-onramp-v*release yet. Its install tasks mirror thesponsordrole's, whichmolecule/sponsordtests.--checkon a brand-new host still fails when it tries to start the unit, as it already does in thesponsordrole.sponsord-onramp.serviceandcaddy.service. That changesconfig.alloyonce on every Grafana-enabled host, so expect one Alloy restart on the next deploy.🤖 Generated with Claude Code