Serve SFTP through the gateway - #18
Merged
Merged
Conversation
The gateway forwards SFTP to the sandbox's own OpenSSH SFTP server over a persistent sbx exec, driven by an SFTP client. One backend per SSH connection is shared by every SFTP session, so a poll loop that opens a fresh session each time starts no new process. The backend closes after an idle period and reopens on the next use, thus an inactive sandbox still sleeps. Port forwarding and scp stay refused.
…he cleanup task A handshake failure now tears down the exec process instead of leaking a live session. The connection-close cleanup task is held until it finishes. Teardown no longer re-raises a receive task that ended with a pipe error.
…n trim out of import Rewrite the added comments and docstrings in Simplified Technical English. Remove the useless test docstrings. Move the SFTP extension trim from an import-time global mutation into start(), so it is a deliberate step when the server starts.
The command that starts the sandbox's SFTP server is a property of the sandbox image, not of the gateway. It moves from a constant in the generic backend to a class attribute on SandboxProcess, set by the sbx process. The gateway backend stays provider-agnostic.
Replace the per-call casts and handle annotations with one narrow pyright pragma in each module. The SFTP handles pass straight from the server to the client, so the annotations added no safety. Disable the unsupported SFTP extensions on the first SFTP session, not at import time, and guard the change so it runs once. Keep the tests that cover the gateway logic (one forwarding round trip, error survival, poll reuse, shared backend, refused forwarding, idle close, failed handshake). Drop the ones that only measured the upstream server.
The command names a path in the sandbox image, so it is not a fixed property of a process class. Move it to a gateway setting with the standard Debian path as the default; an operator sets it for an image with a different path. Drop the run-once guard on the extension filter, which is idempotent.
The command names a path in the provider's image, so the provider owns it, next to default_image. The gateway reads it from the provider when it builds the SFTP backend, and stays free of any image path. The base returns an empty command, which a provider the gateway does not serve keeps.
The sbx image is fixed, so its SFTP server path does not change. Drop the setting and return the standard path from the provider. A later provider that needs a different path can add a seam then.
The command is fixed, so the backend holds it as a constant where it runs it. Drop the provider property and the plumbing that passed it through. The tests point the constant at the host's own sftp-server.
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.
Adds SFTP to the gateway, so callers transfer files the same way they open shells — over the sandbox exec channel. This unblocks consumers that do all file transfer over SFTP and failed the moment they requested the subsystem.
Approach
The daemon owns the data plane, so the gateway has no filesystem to serve directly. Rather than reimplement file operations with shell primitives, the gateway runs the sandbox's own OpenSSH
sftp-serverover a persistentsbx execand forwards SFTP to it with an SFTP client. File semantics — offsets, EOF, attributes, errors, binary safety — come from OpenSSH and asyncssh, not from drukbox.gateway/backend.py: one SFTP backend per SSH connection. It opens the sftp-server on first use, shares it across every SFTP session on the connection, closes after ~30s idle, and reopens on the next use. Thus a poll loop that opens a fresh SFTP session each time starts no new process, and an inactive sandbox still sleeps.gateway/sftp.py: a thinSFTPServerthat delegates each operation to the backend client. Unsupported operations (rename, symlink, …) and port forwarding are refused; scp stays unsupported.gateway/server.py: wires the SFTP factory and closes the backend when the connection ends.Verification
uv run ruff check/ruff format --check/pyright: clean;uv run pytest: 403 pass.