Skip to content

fix: pin device SSH host keys and find the client address behind proxies - #75

Merged
mjunek merged 2 commits into
masterfrom
fix/host-keys-client-address
Nov 15, 2026
Merged

mjunek merged 2 commits into
masterfrom
fix/host-keys-client-address

Conversation

@mjunek

@mjunek mjunek commented Nov 15, 2026

Copy link
Copy Markdown
Contributor

What and why

Two security fixes, one commit each.

Client address behind trusted proxies (fix(orchestrator))

The client address was the last X-Forwarded-For hop, which is the address the web tier saw. Behind the TLS proxy or load balancer a deployment needs, that is the proxy for every request, so:

  • five failed sign-ins from anyone locked out all sign-ins;
  • an API token's allowed networks were compared with the proxy;
  • the audit log recorded only the proxy.

DV_TRUSTED_PROXIES (addresses or networks, comma separated) lists the proxies in front of web. The client address is now the last hop that is not one of them, so hops a client sends itself are never believed. It defaults to empty, which behaves as before, so deployments behind a proxy need to set it.

Device SSH host keys pinned on first use (fix(worker))

Collectors accepted any SSH host key, so a host answering in a device's place received its username, password and enable secret.

  • A device's first collected backup pins the key it presented. The collector reports it in its signed collected event; the orchestrator stores it only if none is pinned and the event comes from a collector of the device's own collection group.
  • Later job.collect tasks carry the pinned key in their signed payload, and the collector refuses any other key before authenticating. The failure is permanent, with the existing reason auth.
  • The device page shows the key's type and SHA256: fingerprint. An administrator of the device's group can forget it when a device is replaced or re-keyed (POST /api/devices/{id}/forget_host_key/).
  • Pinning and forgetting are audited (device.host_key_pin, device.host_key_forget).

Existing devices have no pinned key, so each pins whatever answers at its next backup.

How it was tested

  • make check passes.
  • New tests cover the proxy walk and lockout behind a proxy, the driver's strict checking and error classification, pinning rules on the orchestrator, and the API fields and forget action.
  • The real Netmiko driver was run against a local test SSH server: first use, matching ed25519 and RSA pins, a different key, a key type that is not pinned, and a malformed pin.
  • The device page changes pass lint and type check but were not viewed in a browser.

Checklist

  • make check passes locally
  • Tests added or updated for the change
  • New DV_* variables are documented in docs/DEPLOYMENT.md and the relevant *.env.example
  • Model changes have generated migrations (makemigrations)
  • API changes keep api/serializers.py, api/views.py and frontend/src/api/types.ts / endpoints.ts in step
  • Docs (README.md, docs/) updated where behaviour changed

The client address was the last X-Forwarded-For hop, which is the
address the web tier saw. Behind the TLS proxy or load balancer a
deployment needs, that is the proxy for every request: five failed
sign-ins from anyone locked out all sign-ins, API tokens' allowed
networks were compared with the proxy, and the audit log recorded only
the proxy.

DV_TRUSTED_PROXIES lists the proxies in front of the web tier. The
client address is now the last hop that is not one of them, so hops a
client sends itself are never believed. With none set the behaviour is
unchanged.
Collectors accepted any SSH host key, so a host answering in a device's
place received its username, password and enable secret.

A device's host key is now pinned at its first collected backup: the
collector reports the key it saw in its signed collected event, and the
orchestrator stores it on the device if none is pinned and the event
comes from a collector of the device's own collection group. Later
collect tasks carry the pinned key, and the collector refuses any other
key before authenticating, as a permanent failure with reason auth.

The device page shows the key's type and fingerprint. An administrator
of the device's group can forget it when a device is replaced or
re-keyed (POST devices/{id}/forget_host_key/). Pinning and forgetting
are audited.
@mjunek
mjunek requested a review from a team as a code owner November 15, 2026 04:19
@mjunek
mjunek merged commit a803129 into master Nov 15, 2026
1 check passed
@mjunek
mjunek deleted the fix/host-keys-client-address branch November 15, 2026 04:31
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