Skip to content

fix(docker): upgrade tcp:// to https:// when TLS material is configured#647

Merged
CybotTM merged 1 commit into
mainfrom
fix/issue-634
May 14, 2026
Merged

fix(docker): upgrade tcp:// to https:// when TLS material is configured#647
CybotTM merged 1 commit into
mainfrom
fix/issue-634

Conversation

@CybotTM
Copy link
Copy Markdown
Member

@CybotTM CybotTM commented May 13, 2026

Summary

Closes #634, follow-up to #613.

DOCKER_HOST=tcp://... combined with DOCKER_TLS_VERIFY / DOCKER_CERT_PATH (the canonical docker CLI mTLS setup) was silently going out plaintext. PR #613 wired the cert material into the custom http.Transport, but the SDK kept the tcp:// URL — and Go's http.Transport only triggers TLS for https:// URLs. So the cert material was loaded into a TLSClientConfig the SDK never used. Operators believing they had mTLS were getting unauthenticated connections.

This PR mirrors the docker CLI's long-standing silent upgrade: when the resolved host is tcp:// AND hasTLSMaterial(cfg) is true, rewrite to https:// before passing to client.WithHost. The SDK and transport now agree on the scheme; applyTLSTransport runs; TLS actually negotiates on the wire.

Changes

  • New upgradeTCPToHTTPSIfTLSMaterial(host, cfg) helper, called from NewClientWithConfig once after resolveDockerHost. Pure / deterministic / unit-tested in isolation.
  • Godoc on the tcp: schemeHandlers entry refreshed to describe the new auto-upgrade contract.
  • docs/TROUBLESHOOTING.md scheme table updated.
  • applyTCPTransport's hasTLSMaterial branch retained for direct callers of createHTTPClient (notably TestCreateHTTPClient_TCPWithTLSEnvUpgrades); production now dispatches through applyTLSTransport.

Coordination with sibling PRs

Test plan

  • New TestUpgradeTCPToHTTPSIfTLSMaterial (table-driven, 7 cases): tcp:// upgrades on env or explicit ClientConfig.TLSCertPath; stays plain without TLS; tcp+tls://, https://, unix://, http:// all pass through unchanged.
  • New TestNewClientWithConfig_TCPSchemeUpgradesToHTTPSWithTLSEnv integration test verifies the actual SDK URL via cli.DaemonHost() after construction.
  • New TestNewClientWithConfig_TCPSchemeStaysPlainWithoutTLSEnv negative case.
  • Existing tests unchanged: TestCreateHTTPClient_TCPWithTLSEnvUpgrades, TestCreateHTTPClient_TCPWithoutTLSEnvStaysPlaintext, TestNewClientWithConfig_DoesNotMutateConfigHost, TestSchemeHandlers_AllowListParity, TestHasTLSMaterial_* all still pass.
  • go test ./core/adapters/docker/ -count=1 -short -race clean.
  • golangci-lint run ./... clean.

Copilot AI review requested due to automatic review settings May 13, 2026 21:51
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests labels May 13, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 13, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@github-actions
Copy link
Copy Markdown

✅ Mutation Testing Results

Mutation Score: 100.00% (threshold: 60%)

✨ Good job! Mutation score meets the threshold.

What is mutation testing?

Mutation testing measures test quality by introducing small changes (mutations) to the code and checking if tests detect them. A higher score means better test effectiveness.

  • Killed mutants: Tests caught the mutation (good!)
  • Survived mutants: Tests missed the mutation (needs improvement)

github-actions[bot]
github-actions Bot previously approved these changes May 13, 2026
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval for maintainer PR

All automated quality gates passed. See SECURITY_CONTROLS.md for compensating controls.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a security-impacting Docker SDK integration gap where DOCKER_HOST=tcp://... combined with Docker’s mTLS environment variables could silently result in plaintext connections. It aligns Ofelia’s resolved host scheme with the configured TLS transport by rewriting tcp:// to https:// when TLS cert material is present, mirroring long-standing docker CLI behavior.

Changes:

  • Add a tcp://https:// normalization step in NewClientWithConfig when TLS cert material is configured.
  • Introduce unit + integration tests covering the upgrade behavior and non-upgrade negative cases.
  • Update troubleshooting docs and changelog to describe the new behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
core/adapters/docker/client.go Adds host-scheme rewrite helper and wires it into client construction; updates scheme documentation.
core/adapters/docker/client_tcp_upgrade_test.go Adds table-driven unit tests and constructor-level integration tests for tcp→https upgrade behavior.
docs/TROUBLESHOOTING.md Updates the supported-schemes table to reflect tcp→https upgrade behavior.
CHANGELOG.md Documents the fix and its operator-visible impact.
Comments suppressed due to low confidence (1)

core/adapters/docker/client.go:536

  • The function doc says the tcp:// → https:// rewrite triggers when DOCKER_TLS_VERIFY / DOCKER_CERT_PATH (or ClientConfig.TLSCertPath / TLSVerify) are configured. The implementation only checks cert-path presence via hasTLSMaterial (config TLSCertPath or DOCKER_CERT_PATH), so DOCKER_TLS_VERIFY/TLSVerify alone won’t cause an upgrade. Consider tightening the doc to avoid implying that TLSVerify participates in the decision, and clarify that DOCKER_TLS_VERIFY only affects server verification once TLS is enabled.
// upgradeTCPToHTTPSIfTLSMaterial mirrors the docker CLI's silent scheme
// upgrade: when the resolved host uses the plain tcp:// scheme AND TLS
// material is configured (DOCKER_TLS_VERIFY / DOCKER_CERT_PATH env or the
// equivalent ClientConfig.TLSCertPath / TLSVerify overrides), the scheme is
// rewritten to https:// before being handed to client.WithHost. Without this

Comment thread core/adapters/docker/client.go
Comment thread docs/TROUBLESHOOTING.md Outdated
Comment thread CHANGELOG.md Outdated
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request fixes an issue where using DOCKER_HOST=tcp://... alongside TLS environment variables failed to negotiate TLS end-to-end. The implementation now mirrors the Docker CLI by automatically upgrading the tcp:// scheme to https:// when TLS material is present, ensuring the Go HTTP transport correctly activates TLS. The changes include the new upgrade logic in NewClientWithConfig, updated documentation in the CHANGELOG and troubleshooting guide, and comprehensive tests to verify the scheme rewriting. I have no feedback to provide as there were no review comments to evaluate.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.86%. Comparing base (d3accf7) to head (998a2ca).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #647      +/-   ##
==========================================
- Coverage   87.87%   87.86%   -0.01%     
==========================================
  Files          88       88              
  Lines       10954    10962       +8     
==========================================
+ Hits         9626     9632       +6     
- Misses       1084     1086       +2     
  Partials      244      244              
Flag Coverage Δ
integration 87.84% <100.00%> (-0.03%) ⬇️
unittests 84.95% <100.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

CybotTM added a commit that referenced this pull request May 13, 2026
PR review (`Code review`, `Security review`) flagged that deleting
`hasTLSMaterial` creates a build break for two in-flight sibling PRs:

- #646 (fix/issue-627): fail-closed gate on `tcp+tls://` without cert
  material — calls `hasTLSMaterial(config)` from `NewClientWithConfig`.
- #647 (fix/issue-634): rewrite `tcp://` → `https://` when TLS material
  is set — calls `hasTLSMaterial(cfg)` from `upgradeTCPToHTTPSIfTLSMaterial`.

Restore the helper with a `//nolint:unused` annotation explaining why
it is HEAD-unused. The dead `applyDockerTLS` call is still removed from
`applyTCPTransport` (the actual reconciliation #628 was filed for); the
helper itself is retained so the sibling PRs rebase cleanly.

Refs #628.

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
@CybotTM CybotTM added this pull request to the merge queue May 14, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch May 14, 2026
@CybotTM CybotTM enabled auto-merge May 14, 2026 05:37
github-actions[bot]
github-actions Bot previously approved these changes May 14, 2026
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval for maintainer PR

All automated quality gates passed. See SECURITY_CONTROLS.md for compensating controls.

@CybotTM CybotTM force-pushed the fix/issue-634 branch 2 times, most recently from 629efd4 to 44ff5d6 Compare May 14, 2026 05:54
Mirror the docker CLI's silent scheme upgrade: when DOCKER_HOST=tcp://...
is combined with DOCKER_TLS_VERIFY / DOCKER_CERT_PATH (or the equivalent
ClientConfig.TLSCertPath / TLSVerify overrides), rewrite the host scheme
to https:// before passing it to client.WithHost.

Without the rewrite, the SDK was given a tcp:// URL while the custom
HTTP transport was wired with TLS material via #613. Go's http.Transport
only triggers TLS for https:// URLs, so the cert material was silently
unused and connections went out plaintext, leaving operators believing
they had mTLS when they did not.

The rewrite is implemented as a tested helper
(upgradeTCPToHTTPSIfTLSMaterial) called once from NewClientWithConfig
right after host resolution. Other schemes (tcp+tls://, https://,
unix://, http://, npipe://) pass through unchanged. The
applyTCPTransport hasTLSMaterial branch stays in place to support
direct callers of createHTTPClient (notably
TestCreateHTTPClient_TCPWithTLSEnvUpgrades); in production this rewrite
means dispatch goes through applyTLSTransport.

Updates the godoc on the tcp: schemeHandlers entry and the
TROUBLESHOOTING.md scheme table to make the new contract explicit.

Closes #634, follow-up to #613.

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
18.2% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval for maintainer PR

All automated quality gates passed. See SECURITY_CONTROLS.md for compensating controls.

@CybotTM CybotTM added this pull request to the merge queue May 14, 2026
Merged via the queue into main with commit 9bdec3d May 14, 2026
26 of 27 checks passed
@CybotTM CybotTM deleted the fix/issue-634 branch May 14, 2026 06:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Docker SDK: tcp:// + TLS env vars wires TLSClientConfig but the URL stays http://

2 participants