Enforce a TLS posture on non-loopback HTTP binds#67
Open
deviantony wants to merge 1 commit into
Open
Conversation
Over HTTP the server carries two secrets on the wire (the shared gate
token and each caller's permanent Portainer API key), so it now refuses
to boot on a non-loopback bind unless the operator declares one of three
transport postures:
- server-terminated TLS: PORTAINER_MCP_TLS_CERT/_TLS_KEY -> uvicorn
ssl_certfile/ssl_keyfile. Self-signed leaf certs WARN (never block).
- TLS-terminating proxy: PORTAINER_MCP_TRUST_PROXY_TLS=1 +
PORTAINER_MCP_FORWARDED_ALLOW_IPS -> trusted X-Forwarded-Proto.
- plaintext opt-out: PORTAINER_MCP_DANGEROUSLY_ALLOW_PLAINTEXT_HTTP=1,
the one loud escape hatch (WARNs every start, marks the audit log
insecure_transport: true).
Both encrypted shapes converge on scheme == "https", enforced by
TLSRequiredMiddleware as a backstop. It is installed via the verifier's
get_middleware() so it runs before the bearer-auth backend -- a plaintext
request is rejected before the per-user key is validated and forwarded
upstream. Loopback binds stay exempt for dev. A broken declaration
hard-fails at startup, never silently downgrades.
Adds tls.py, the auth.py wiring (pre-auth middleware hook +
insecure_transport audit flag), main() resolution, tests, and docs.
cryptography promoted to a direct dependency (self-signed detection).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Over HTTP the server carries two secrets on the wire (the shared gate token and each caller's permanent Portainer API key), so it now refuses to boot on a non-loopback bind unless the operator declares one of three transport postures:
Both encrypted shapes converge on scheme == "https", enforced by TLSRequiredMiddleware as a backstop. It is installed via the verifier's get_middleware() so it runs before the bearer-auth backend -- a plaintext request is rejected before the per-user key is validated and forwarded upstream. Loopback binds stay exempt for dev. A broken declaration hard-fails at startup, never silently downgrades.
Adds tls.py, the auth.py wiring (pre-auth middleware hook + insecure_transport audit flag), main() resolution, tests, and docs. cryptography promoted to a direct dependency (self-signed detection).
Related to #65 and #66