Skip to content

Finish secure agent connection (cert pinning discoverability + vendored sync + MSI source) - #36

Merged
Mischa323 merged 4 commits into
mainfrom
claude/finish-secure-connection
Jun 21, 2026
Merged

Finish secure agent connection (cert pinning discoverability + vendored sync + MSI source)#36
Mischa323 merged 4 commits into
mainfrom
claude/finish-secure-connection

Conversation

@Mischa323

Copy link
Copy Markdown
Owner

Completes the secure-connection work that was started but never landed. The core
logic shipped earlier — agent cert pinning + per-device-secret client
(leuffen-rmm-agent) and server-side device-secret issue/validate (PR #35) — but
several loose ends meant it wasn't actually usable or consistent end to end. This
ties them off.

Pairs with leuffen-rmm-agent#4 (canonical agent gains config-file fingerprint);
merge that first so the vendored copy here stays in step.

What was unfinished

  1. Cert pinning was undiscoverable. The agent could pin RMM_SERVER_FINGERPRINT
    but the server never exposed its own fingerprint, so an admin had no way to know
    what to pin.
  2. Vendored agent was stale. The server image bundles agent/ (COPY agent /agent) and serves it via agent.zip, but that copy lacked cert pinning, the
    device-secret client, and the login/lock-screen capture — so zip-installed
    agents differed from MSI ones. (The old claude/sync-vendored-agent branch only
    half-synced it and was never merged — this supersedes it.)
  3. MSI download pointed at the wrong repo. RMM_MSI_URL/RMM_GH_REPO still
    defaulted to this repo (stale v1.1.x agent) after the build moved to
    leuffen-rmm-agent (v2.x with the secure code).
  4. Undocumented. None of SESSION_SECRET / RMM_SERVER_FINGERPRINT /
    RMM_REQUIRE_DEVICE_SECRET were in the docs.

Changes

  • tls.cert_fingerprint() — SHA-256 of the leaf cert DER (PEM→DER via base64,
    no new deps); matches what the agent hashes from getpeercert(binary_form=True).
    Verified equal to cryptography's fingerprint(SHA256) and to openssl.
  • GET /api/server-fingerprint (global admin) → {fingerprint, tls_mode};
    null in proxy mode.
  • Startup log prints the fingerprint (non-proxy modes).
  • Settings → Security shows a read-only fingerprint card with copy button +
    pinning instructions (lazy-loaded like the logs tab).
  • Vendored agent synced verbatim to canonical v2.2.10 (agent.py, handlers.py,
    inventory.py, screen.py, updater.py).
  • MSI defaults repointed to Mischa323/leuffen-rmm-agent (asset
    leuffen-rmm-agent.msi, confirmed on v2.2.10).
  • Docs — README "Secure connection" section (env-var table + safe rollout
    order) and CHANGELOG.

Verification

  • py_compile on all server + vendored-agent modules; node --check on
    settings.js.
  • Fingerprint helper validated against cryptography and openssl (exact match;
    agent-side colon/upper normalisation round-trips).

Operational follow-ups (not code)

  • Release leuffen-rmm-agent so the MSI carries agent#4.
  • After the fleet is on the new agent and each has reconnected once, set
    RMM_REQUIRE_DEVICE_SECRET=1 to enforce the device secret.
  • The stale claude/sync-vendored-agent branch can be deleted (superseded here).

🤖 Generated with Claude Code

Mischa323 and others added 4 commits June 21, 2026 13:50
…screen)

The server image bundles agent/ (Dockerfile: COPY agent /agent) and serves it via
/api/orgs/{org}/agent.zip, so the vendored copy must match the canonical agent in
mischa323/leuffen-rmm-agent. It had drifted: the vendored agent lacked TLS cert
pinning and the per-device-secret client, and screen.py lacked the login/lock
screen capture. This makes vendored == canonical so zip-installed agents get the
same secure-connection code as MSI-installed ones.

Files synced verbatim from leuffen-rmm-agent: agent.py, handlers.py, inventory.py,
screen.py, updater.py (Dockerfile, netscan.py, requirements.txt, tray.py already
matched).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cert pinning shipped agent-side (RMM_SERVER_FINGERPRINT / server_fingerprint) but
the server never surfaced its own fingerprint, so admins had no way to know what to
pin. Add:
- tls.cert_fingerprint(): SHA-256 of the leaf cert DER — exactly what the agent
  hashes from getpeercert(binary_form=True). Decodes PEM->DER via base64 (no extra
  deps). Returns None when the cert isn't readable (e.g. proxy mode).
- GET /api/server-fingerprint (global admin): {fingerprint, tls_mode}.
- run.py logs the fingerprint on startup (non-proxy modes).
- Settings -> Security: read-only fingerprint card with copy button and pinning
  instructions (lazy-loaded like the logs tab).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Windows agent build moved to mischa323/leuffen-rmm-agent (which publishes the
v2.x MSIs with the secure-connection code), but MSI_URL/GH_REPO still defaulted to
Mischa323/Leuffenrrm — whose latest release is a stale v1.1.x agent. Unless prod
set RMM_MSI_URL/RMM_GH_REPO, /install.msi and the version badge pointed at the
wrong repo. Point the defaults at leuffen-rmm-agent (asset: leuffen-rmm-agent.msi,
confirmed on v2.2.10).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…out)

README: new "Secure connection" section covering cert pinning (how to obtain and
pin the fingerprint), the per-device secret, the env-var table (SESSION_SECRET,
RMM_SERVER_FINGERPRINT, RMM_REQUIRE_DEVICE_SECRET), and a safe rollout order for
enabling enforcement. CHANGELOG: Unreleased entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.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.

1 participant