Skip to content

MIR-1478: Keep the OCI registry on the internal WireGuard network - #1264

Merged
evanphx merged 3 commits into
mainfrom
evan/mir-1478-firewallmd-doesnt-tell-operators-to-keep-port-5000-off-the
Sep 25, 2026
Merged

evanphx merged 3 commits into
mainfrom
evan/mir-1478-firewallmd-doesnt-tell-operators-to-keep-port-5000-off-the

Conversation

@evanphx

@evanphx evanphx commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

What changed

  • Bind the coordinator OCI registry only to its Miren rt0 bridge gateway (10.8.x.1:5000), and restrict inbound traffic to local/bridge and WireGuard interfaces.
  • Advertise that gateway to distributed runners as coordinator_internal_ip; map cluster.local to it for image pulls while leaving coordinator API access on its configured address. Existing cluster.local:5000 image references and bearer-token checks remain unchanged.
  • Let upgraded runners temporarily use an older coordinator’s public registry address, while reporting coordinator-info RPC failures directly at startup. Document why TCP 5000 needs no public opening on the new release and must be blocked from untrusted traffic on older releases.

Verification

  • make lint: 0 issues.
  • iso run go test -p 1 ./network ./components/coordinate ./components/runner ./components/distributedrunner ./components/ocireg ./servers/runner ./components/server -skip '^TestRunnerCoordinatorIntegration$' -count=1: all seven packages passed. A focused ISO test verified the firewall rules precede an old bridge allow rule; runner tests cover internal routing and legacy coordinator fallback.
  • cd docs && bun run lint && bun run build: passed. Both changed callouts were rendered and visually inspected.
  • Earlier disposable two-peer check: coordinator listened only on 10.8.99.1:5000, runner routed to it through flannel-wg, unauthenticated /v2/ returned 401, public-facing port 5000 refused connections, and a BuildKit push/runner pull of cluster.local:5000/... deployed a healthy app.
  • Unfiltered affected-package suite failed only at TestRunnerCoordinatorIntegration because the test container could not connect to /run/containerd/containerd.sock (timeout); it passed with that environment-dependent test excluded.

Rollout / tradeoffs

This one-time transition can interrupt image pulls on a runner between the coordinator cutover and that runner’s restart. Miren Cloud-managed upgrades update the coordinator first and then restart runners one at a time, closing each runner’s gap automatically. For manual upgrades, upgrade runner binaries first while the old coordinator serves the registry, then upgrade the coordinator and restart the runners again to learn its WireGuard gateway. Runners cache the gateway at startup and need restarting if the coordinator subnet lease changes. External BuildKit clients need their own private route and name mapping. HTTP registry requests still require bearer tokens; WireGuard encrypts the inter-host transport.

@evanphx
evanphx requested a review from a team as a code owner September 24, 2026 03:57
@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 7d6eeccc-e2f3-4b80-8777-f42f4d652c29

📥 Commits

Reviewing files that changed from the base of the PR and between 2d615e0 and ee18497.

📒 Files selected for processing (16)
  • api/runner/rpc.yml
  • api/runner/runner_v1alpha/rpc.gen.go
  • components/coordinate/coordinate.go
  • components/coordinate/runner_endpoints.go
  • components/distributedrunner/boot_sandbox_host.go
  • components/distributedrunner/boot_sandbox_host_test.go
  • components/runner/runner.go
  • components/server/boot_foundation.go
  • components/server/boot_oci_registry.go
  • components/server/startup.go
  • docs/docs/distributed-runners.md
  • docs/docs/firewall.md
  • network/bridge.go
  • network/bridge_registry_test.go
  • servers/runner/registration.go
  • servers/runner/registration_test.go

Included review availability: 1 review is currently available. Your included PR review attempts over the past 7 days set your current allowance at 6 reviews per hour.


📝 Walkthrough

Walkthrough

The coordinator now binds its OCI registry to the mapped internal address and installs firewall rules for local and WireGuard traffic. Runner registration reports that address, and runner startup validates and uses it to resolve the registry host. The changes also add tests and document the required upgrade and restart sequence.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to ee184

The coordinator's image registry now listens only on the internal bridge address. Firewall rules limit it to local and WireGuard traffic, and runners pull images over WireGuard. The upgrade path keeps older-coordinator compatibility through the API-address fallback, and the required runner restart after a coordinator upgrade is documented. No outstanding merge-blocking issues remain.


Comment @coderabbitai help to get the list of available commands.

miren-code-agent[bot]

This comment was marked as outdated.

miren-code-agent[bot]

This comment was marked as outdated.

@phinze phinze self-assigned this Sep 24, 2026

@phinze phinze 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.

🤖 Functionally this looks good to go. Binding the registry to the bridge gateway and reaching it over WireGuard is a nice answer to the question MIR-1477 left open, and it's a real security win beyond reachability: since #991, runners have been sending bearer tokens and pulling app images over plain HTTP across the public network, and now all of that rides the encrypted overlay. The per-runner pull gap during an upgrade roll seems acceptable to us.

The two docs callouts could use a cleanup pass, though. Notes inline, neither blocking.

Comment thread docs/docs/firewall.md Outdated
Comment on lines +29 to +31
:::danger[Keep the registry off the internet]
TCP port 5000 is cluster-internal. Do not expose it externally, including on bare-metal hosts without a cloud security group. The OCI registry listens on the coordinator's Miren bridge gateway, not on its public interfaces.
:::

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.

🤖 nit: this reads a little oddly now. It's a danger telling operators not to expose 5000, and then the last sentence says the registry doesn't listen on public interfaces anyway, so there's nothing left for them to do. I think this PR moved the right guidance from "block it" to "you never need to open it." That could be a plain note saying 5000 is cluster-internal and never needs opening, plus the version where this changed so anyone still on an older release (which listens on all interfaces) knows to block it themselves.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🤖 The callout now says public TCP 5000 never needs opening on the internal-only release, and tells operators of older releases to restrict access to trusted runners. I left the release number unset until it is assigned. Docs lint/build and a rendered-page check passed.

Comment thread docs/docs/distributed-runners.md Outdated
Comment on lines +202 to +204
:::warning[Upgrade runners before changing the registry address]
Upgrade runner binaries first, while the coordinator still serves the registry on its old address. Then upgrade the coordinator and restart the runners so they learn its internal WireGuard address. Image pulls on runners that have not restarted after the coordinator upgrade will fail; plan a maintenance window for this transition.
:::

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.

🤖 The framing here is a bit off. "Before changing the registry address" makes it sound like something an operator does, but nobody changes that address by hand. What moves it is upgrading to this release. I think this wants to read as a one-time note for the version where it lands: "when upgrading from vX to vY, ..."

It's also worth squaring with how upgrades actually run. Managed upgrades (miren-cloud/upgrades.md) do the server first and then roll runners one at a time, which is the opposite of the order here. That still works out: each runner can't pull images from the moment the server upgrades until its own upgrade restarts it. Saying that directly would keep a managed-upgrade user from thinking they need to intervene, and the runners-first order can stay as the advice for anyone upgrading by hand.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I agree, and I missed this. When I signed off on the runners-first order, I didn't check it against miren-cloud/upgrades.md. That doc says the server upgrades itself first and then rolls runners "one at a time", so managed users will never do what this warning tells them to. As you say, the managed order still ends up fine. Each upgraded runner restarts on a binary that reads coordinator_internal_ip, so pulls only fail on a given runner between the server upgrade and that runner's own restart. Writing it as a one-time "upgrading from vX to vY" note that says that window closes on its own for managed upgrades, and keeping runners-first as the advice for manual upgrades, would make it both accurate and less alarming.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🤖 The one-time upgrade note now describes managed coordinator-first upgrades and the per-runner pull gap, alongside runner-first advice for manual upgrades. I did not hard-code a release number before it is assigned. Docs lint/build and a rendered-page check passed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I checked the rewritten note against the code, and it's accurate. It says managed upgrades go coordinator-first with a pull gap on each runner that ends when that runner restarts. That matches the runner reading coordinator_internal_ip at startup. Runners-first still holds for manual upgrades, because of the API-address fallback in prepareNetworkDeps. Holding off on the version number until it's assigned makes sense to me, but please fill in the "upgrading from vX to vY" wording once the release is cut, since that's how readers will tell whether the note applies to them. phinze, it's your thread, so I'll leave closing it to you.

@miren-code-agent miren-code-agent Bot 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.

🍪 biscuit: ✅ ready to merge — auto-review, non-blocking

Still ready to merge. The only change since my last review is in the two docs callouts, which were rewritten after phinze's comments. The code is the same as what I approved.

distributed-runners.md. The upgrade note now matches how upgrades actually run. It says managed upgrades do the coordinator first and then roll runners one at a time, and that each runner starts pulling again once its own upgrade restarts it. That fits the code: the runner reads coordinator_internal_ip at startup, so the pull gap on each runner ends when it restarts, and nobody has to step in. For manual upgrades, the runners-first order still holds up, because a new runner talking to an old coordinator uses the API-address fallback in prepareNetworkDeps. This fixes the problem I missed when I first signed off on the runners-first wording.

firewall.md. The callout now says 5000/tcp never needs to be opened publicly, and warns anyone still on an older release, which listens on all interfaces, to block it themselves. That's the change in emphasis phinze asked for.

phinze suggested naming the actual versions ("when upgrading from vX to vY"). Both callouts still say "the release that routes registry pulls over WireGuard". I assume the release number isn't settled yet. If it is, putting it in would help anyone scanning the docs to see whether this applies to them. That's phinze's thread to settle, and I won't hold the merge for it.

My earlier non-blocking caveat still applies: runners read the coordinator's bridge gateway only at startup, so if the coordinator comes back on a new flannel subnet, they keep pulling from the old address until they restart.


🍪 full review note · reviewed at 253fe64 · comment /biscuit review to run biscuit again.

@evanphx
evanphx merged commit a91d95e into main Sep 25, 2026
30 checks passed
@evanphx
evanphx deleted the evan/mir-1478-firewallmd-doesnt-tell-operators-to-keep-port-5000-off-the branch September 25, 2026 04:01
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.

2 participants