Repository navigation
fix: pin device SSH host keys and find the client address behind proxies - #75
Merged
Merged
Conversation
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.
This was referenced Nov 15, 2026
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.
What and why
Two security fixes, one commit each.
Client address behind trusted proxies (
fix(orchestrator))The client address was the last
X-Forwarded-Forhop, which is the address thewebtier saw. Behind the TLS proxy or load balancer a deployment needs, that is the proxy for every request, so:DV_TRUSTED_PROXIES(addresses or networks, comma separated) lists the proxies in front ofweb. 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.
collectedevent; the orchestrator stores it only if none is pinned and the event comes from a collector of the device's own collection group.job.collecttasks carry the pinned key in their signed payload, and the collector refuses any other key before authenticating. The failure is permanent, with the existing reasonauth.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/).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 checkpasses.Checklist
make checkpasses locallyDV_*variables are documented indocs/DEPLOYMENT.mdand the relevant*.env.examplemakemigrations)api/serializers.py,api/views.pyandfrontend/src/api/types.ts/endpoints.tsin stepREADME.md,docs/) updated where behaviour changed