Skip to content

context: load TLS for contexts without a client cert - #3446

Open
breken-ai wants to merge 1 commit into
docker:mainfrom
breken-ai:fix-context-tls-without-client-cert
Open

breken-ai wants to merge 1 commit into
docker:mainfrom
breken-ai:fix-context-tls-without-client-cert

Conversation

@breken-ai

Copy link
Copy Markdown

What's wrong

Context._load_certs builds a TLSConfig only when an endpoint has ca, cert and key. The Docker CLI turns TLS on for a context endpoint when it has any TLS material, or when skip-tls-verify is set. The client cert is optional. So two common contexts load in docker-py without TLS:

docker context create caonly --docker "host=tcp://daemon:2376,ca=/path/ca.pem"
docker context create insecure --docker "host=tcp://daemon:2376,skip-tls-verify=true"

docker --context caonly version speaks TLS. DOCKER_CONTEXT=caonly python -c 'import docker; docker.from_env().ping()' sends plain HTTP to the TLS port and fails. Since from_env() now falls back to the current context (#3401, 7.2.0), this also hits docker.from_env().

Reproduction on main: a local HTTPS server with a self-signed cert, and both contexts created with the Docker CLI 27.5:

caonly: base_url http://127.0.0.1:23771  ConnectionError ('Connection aborted.', ConnectionResetError(54, ...))
skipv:  base_url http://127.0.0.1:23771  ConnectionError ('Connection aborted.', ConnectionResetError(54, ...))

On this branch:

caonly: base_url https://127.0.0.1:23771  ping True   (verified against the context's ca.pem)
skipv:  base_url https://127.0.0.1:23771  ping True   (verification off)

A related crash: Context.TLSMaterial (used by inspect()/str(ctx)) unpacked tls.cert unconditionally. A TLS config without a client cert would raise a TypeError there.

Fix

  • For the docker endpoint, build the TLSConfig when any of ca/cert/key exists or SkipTLSVerify is true. Pass client_cert only when both cert and key exist. verify stays as it was: True unless skip-tls-verify is set. Other endpoints keep the old all-three rule.
  • A meta file without SkipTLSVerify now means false, which is the CLI's zero value. Before, it defaulted to true. That default only mattered for contexts with full certs, where it quietly turned verification off. With the first change it would also have turned TLS on for such endpoints.
  • TLSMaterial lists only the files that exist.

Tests

  • New LoadContextTLSTest in tests/unit/context_test.py. Each test writes a context in the CLI's on-disk layout under a temporary DOCKER_CONFIG. The CA-only and skip-tls-verify tests fail on main (assert None is not None) and pass here. The full-mTLS and plain-TCP cases are controls and pass on both.
  • pytest tests/unit: 618 passed, run with an isolated HOME. Two existing tests read the developer's own ~/.docker config and fail the same way on main.
  • ruff check docker tests is clean.

This was found and fixed with help from an AI coding assistant (Claude). I checked the change and ran the end-to-end reproduction above. The commit is signed off and carries an Assisted-By: trailer.

A context endpoint created with `docker context create --docker
host=tcp://...,ca=ca.pem` (CA only) or `skip-tls-verify=true` (no TLS
files) uses TLS in the Docker CLI. Context._load_certs only built a
TLSConfig when ca, cert and key were all present, so these contexts
got no TLS config and from_env()/from_context() talked plain HTTP to
the TLS port.

Build the docker endpoint's TLSConfig when it has any TLS material or
skip-tls-verify is set, pass the client cert only when both cert and
key exist, and treat a missing SkipTLSVerify as false like the CLI.
TLSMaterial no longer assumes a client cert.

Assisted-By: Claude
Signed-off-by: breken-ai <312387581+breken-ai@users.noreply.github.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