Skip to content

chore: sync with basecamp/kamal-proxy main (on-demand TLS, client-IP header, Go 1.26.5) - #42

Merged
mhenrixon merged 17 commits into
dashfrom
chore/sync-upstream-2026-07
Jul 27, 2026
Merged

chore: sync with basecamp/kamal-proxy main (on-demand TLS, client-IP header, Go 1.26.5)#42
mhenrixon merged 17 commits into
dashfrom
chore/sync-upstream-2026-07

Conversation

@mhenrixon

Copy link
Copy Markdown
Collaborator

Summary

Syncs dash with basecamp/kamal-proxy:main through 2ad0b2c — the 9 upstream commits dash was behind.

What comes in from upstream:

Upstream change Notes for the fork
On-demand TLS (tls_on_demand.go, --tls-on-demand-url) This is ROADMAP R4 "port PR #63" — upstream shipped it, so the port is off our list. It reaches the handshake through the fork's Router.GetCertificate, which falls through to the catch-all service's cert manager.
--client-ip-header (client_ip_middleware.go) New outermost middleware in createMiddleware.
--exclude-metrics-path ServiceOptions.ShouldExcludeMetricsloggingRequestContext.ExcludeMetrics.
Configurable socket path (KAMAL_PROXY_SOCKET) Touches Config.SocketPath(); the socket name is unchanged, so the kamal-proxy naming contract holds.
Path-prefix-aware path matching (RoutedTargetPath) Replaces the inline prefix strip in Target.rewrite.
Go 1.26.5 + dependency bump Dockerfiles, go.mod/go.sum.

Conflict resolutions

Six conflicts: go.mod, go.sum, service.go, service_test.go, router_test.go, deploy_test.go. Test-file conflicts were pure unions (both sides only added tests). The substantive ones:

  • go.mod/go.sum — upstream's toolchain (1.26.5) and dependency versions, fork's go-acme/lego graph retained, then go mod tidy.
  • ServiceOptions — union: upstream's ExcludeMetricsPaths/ClientIPHeader next to the fork's TLSDomains* fields.
  • Validate — upstream's on-demand validation branch, with the fork's dynamic-domains exemption preserved on the else branch (!HasConfiguredHosts() && TLSDomainsSource == "").
  • createCertManager — the proxy-wide SAN manager still wins by default, but an explicit --tls-on-demand-url is a per-service opt-in and now takes precedence, falling through to upstream's autocert.Manager + createHostPolicy.
  • createMiddleware — kept the fork's targetOptions parameter (the request-deadline middleware from feat(target): per-route timeouts and an opt-in whole-request deadline #31) and added upstream's createHostPolicy.
  • Prefix matching — upstream dropped the strings import from target.go, so the per-path timeout matcher now shares a new PathMatchesPrefix with ResolvePathTimeout rather than duplicating the check.

Test plan

  • make test — all packages pass
  • go test -race ./internal/... — clean (3 consecutive full-package runs)
  • gofmt -l internal/ cmd/ and go vet ./... clean
  • go build ./...
  • New tests for both judgment calls below

Deviations & judgment calls

  • tls-domains-source + tls-on-demand-url is now a validation error. Both provision certificates for hosts unknown at deploy time, but through different managers, and only one can serve a given handshake. Silently letting one win would be a debugging trap, so validateDynamicDomains rejects the combination. Covered by TestServiceOptions_Validate_DynamicDomains.
  • On-demand beats the SAN manager when both are available. --acme-email installs a proxy-wide SAN manager that previously short-circuited createCertManager; a service that explicitly sets --tls-on-demand-url would then have had that flag silently ignored. An explicit per-service flag should win over a proxy-wide default. Covered by a new assertion in the same test.
  • Upstream's standalone on-demand implementation was kept as-is. ROADMAP R4 said to integrate on-demand with the fork's CertificateRegistry rather than the PR's standalone path. Preserving upstream code verbatim is the cheaper merge and the smaller diff for future syncs; wiring on-demand into the registry (so DNS-01/wildcard and on-demand share one cache) is left as a follow-up rather than folded into a sync PR.
  • Merged upstream/main directly into a branch off dash, not through main. main still carries a fork-local Create FUNDING.yml commit, so git merge --ff-only upstream/main on main fails; routing the sync through it would have meant either a merge commit on the "ff-only mirror" or a force-push. The content is identical either way. main is still 9 commits behind and wants a separate decision.
  • Not fixed here: TestHealthCheckWithCustomHost uses a 10ms timeout and flakes under -race when the whole (now larger) package runs on a loaded machine. health_check.go and its test are untouched by this merge, and the full -race suite passes on reruns, so it's pre-existing test fragility rather than a regression.

braun2morrow and others added 17 commits April 23, 2026 13:30
Read the envvar KAMAL_PROXY_SOCKET to determine the control socket path.
This makes it possible to control a running kamal-proxy instance from a
different user, by making the socket accessible via file system
permissions.

If KAMAL_PROXY_SOCKET is unset, retain the previous
${XDG_RUNTIME_DIR:/tmp}/kamal-proxy.sock
behaviour.
Lets services opt specific paths (typically health checks from upstream
load balancers or uptime monitors) out of the Prometheus request and
in-flight metrics. Matches are exact, can be repeated, and only suppress
metrics — request logs are still emitted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The motivation for `--exclude-metrics-path` is that high-volume
healthcheck traffic distorts aggregate metrics (request rate, latency
percentiles, error rates) and inflates the metrics pipeline — not that
the output is "noisy".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add --exclude-metrics-path to filter request paths from metrics
There are a couple places where behviour is dependent on whether a
request path matches a configured value: identifying incoming health
check requests; and identifying paths for which metrics should not be tracked.

In both of these cases, if an app is deployed under a path prefix (and
strips the prefix) then the expected behaviour is that we match that
path against what the upstream sees. So we need to factor in any
prefix-stripping before paths are matched.
…matching

Respect path prefix when matching paths
Typically a downstreeam proxy will pass the original client IP via
`X-Forwarded-For`, which we already handle. However some proxies use a
different header. For example, Cloudflare typically sets it in
`True-Client-IP`.

To support this, add a new `--client-ip-header` deploy flag which
specifies the name of the header to use. When this is set, we copy the
content of that header into `X-Forwarded-For` before logging and
proxying, as if `X-Forwarded-For` had been set that way in the request.
Allow specifying header source for client IP
Allows applications to provision TLS certificates for multiple hosts
on-demand, by way of an application endpoint that gates issuance on a
host-by-host basis.

To use, specify `--tls-on-demand-url` rather than `--host`. The URL can
be directed to an external service, or plain path routed to the service.

Co-authored-by: Didier Lafforgue <didier.lafforgue@gmail.com>
Brings in basecamp's on-demand TLS, client-IP header source,
--exclude-metrics-path, configurable socket path, path-prefix-aware path
matching, and the Go 1.26.5 / dependency bump.

Conflict resolutions:

- go.mod/go.sum: upstream's toolchain (1.26.5) and dependency versions,
  fork's lego graph retained, then go mod tidy.
- ServiceOptions: union -- upstream's ExcludeMetricsPaths/ClientIPHeader
  alongside the fork's TLSDomains* fields.
- Validate: upstream's on-demand branch, with the fork's dynamic-domains
  exemption preserved on the else-branch. tls-domains-source and
  tls-on-demand-url are now rejected together: both provision certificates
  for hosts unknown at deploy time, but only one manager can serve the
  handshake.
- createCertManager: the shared SAN manager still wins by default, but an
  explicit --tls-on-demand-url is a per-service opt-in and takes precedence,
  falling through to upstream's autocert manager and host policy.
- createMiddleware: kept the fork's targetOptions parameter (request
  deadline) and added upstream's createHostPolicy.
- Prefix matching: upstream dropped the strings import from target.go, so
  the per-path timeout matcher now shares PathMatchesPrefix with
  ResolvePathTimeout instead of duplicating the check.
…erge

- tls-domains-source combined with tls-on-demand-url is rejected
- an explicit --tls-on-demand-url takes precedence over the proxy-wide SAN
  certificate manager instead of being silently ignored
@mhenrixon mhenrixon self-assigned this Jul 27, 2026
@mhenrixon mhenrixon added enhancement New feature or request proxy dash-proxy (Go) work labels Jul 27, 2026
@mhenrixon
mhenrixon merged commit bae6356 into dash Jul 27, 2026
2 checks passed
@mhenrixon
mhenrixon deleted the chore/sync-upstream-2026-07 branch July 29, 2026 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request proxy dash-proxy (Go) work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants