Skip to content

Support an authenticating egress proxy for outbound HTTP clients - #1076

Open
ewels wants to merge 1 commit into
masterfrom
claude/wave-proxy-auth-ix28b5
Open

Support an authenticating egress proxy for outbound HTTP clients#1076
ewels wants to merge 1 commit into
masterfrom
claude/wave-proxy-auth-ix28b5

Conversation

@ewels

@ewels ewels commented Jul 7, 2026

Copy link
Copy Markdown
Member

Summary

Addresses nextflow-io/nextflow#7305.

An on-prem Wave deployment behind a corporate egress proxy could not reach container registries or Seqera Platform: the outbound java.net.http.HttpClient instances created by HttpClientFactory had no proxy selector and — crucially — no proxy authenticator (java.net.http.HttpClient ignores Authenticator.setDefault(); the authenticator must be set on the client builder).

This PR adds egress proxy support to HttpClientFactory, the single chokepoint through which all outbound clients are created (ProxyClient, StreamServiceImpl, registry auth/lookup, Tower connector, etc.).

Changes

  • HttpProxyConfig (new): models the proxy settings — host, port, credentials and no-proxy list. Parses [http[s]://][user:pass@]host[:port] URIs (percent-encoded credentials supported) and provides:
    • a ProxySelector that routes requests via the proxy, honouring the no-proxy list (exact hosts, .domain/*.domain suffixes, IPv4 CIDR blocks, *). Patterns are compiled at construction time since the selector runs on every outbound request. Loopback targets always bypass the proxy, unless the proxy itself is a loopback address;
    • an Authenticator scoped to RequestorType.PROXY and the configured proxy host/port, so credentials are only ever released to the proxy itself.
  • HttpClientFactory: clients are now built with the proxy selector and authenticator when a proxy is configured, via a setProxyConfig(...) bridge that invalidates the cached clients. With no proxy configured, no .proxy(...) call is made and behaviour is unchanged (requests still route via ProxySelector.getDefault(), i.e. -Dhttps.proxyHost et al. keep working).
  • HttpClientConfig (now a @Context bean): owns the new settings wave.httpclient.proxy.uri, .username, .password, .no-proxy alongside the existing wave.httpclient.* options, resolves them at startup — falling back to the HTTPS_PROXY/HTTP_PROXY/NO_PROXY environment variables when not set — and applies the result to HttpClientFactory.
  • Docs: new Egress proxy section under HTTP client in docs/configuration.md, including an explicit statement of scope (the Wave process's internal HTTP clients; Micronaut declarative clients, AWS SDK clients, and out-of-process build/scan/blob-cache jobs have their own proxy configuration paths).

The disabledSchemes dependency

By default the JDK ships jdk.http.auth.tunneling.disabledSchemes=Basic, which silently blocks Basic authentication on the HTTPS CONNECT tunnel — i.e. exactly the path used to reach https:// registries through a proxy. When a proxy with credentials is configured, HttpClientConfig defaults both jdk.http.auth.tunneling.disabledSchemes and jdk.http.auth.proxying.disabledSchemes to empty at startup, unless the operator has already set them, in which case the operator's value wins.

Because these properties are read once by the JDK HTTP stack, setting them explicitly on the JVM is the belt-and-braces alternative for containerised deploys, documented in docs/configuration.md:

JAVA_TOOL_OPTIONS="-Djdk.http.auth.tunneling.disabledSchemes= -Djdk.http.auth.proxying.disabledSchemes="

Tests

  • HttpProxyConfigTest: URI/credential parsing, env-var fallback and precedence, no-proxy matching (suffix/wildcard/CIDR/loopback), selector routing, authenticator scoping.
  • HttpClientFactoryProxyTest: integration test against an in-process authenticating proxy (FakeProxyServer, supporting Basic auth, plain forwarding and CONNECT tunnelling):
    • 407 surfaced when the proxy requires auth and no credentials are configured — both redirect policies;
    • successful authenticated requests through the proxy — both redirect policies;
    • no-proxy list bypass;
    • HTTPS CONNECT tunnelling with Basic proxy auth against a local TLS server (self-signed keystore generated on the fly with keytool), which requires -Djdk.http.auth.tunneling.disabledSchemes= — added to the test task in build.gradle.

All 45 new tests pass. Pre-existing network-dependent suites (ProxyClientTest, RegistryLookupServiceTest) fail identically with and without this change in the sandbox environment (registry credentials/egress policy), i.e. no regression.

Backwards compatibility

No proxy configured (neither settings nor environment variables) → no behaviour change. Note that deployments that already export HTTPS_PROXY/HTTP_PROXY will now have those honoured by Wave's outbound clients; NO_PROXY is honoured accordingly.

🤖 Generated with Claude Code

https://claude.ai/code/session_018zJyj9FkzUpMNuH51ScqAQ

@ewels
ewels force-pushed the claude/wave-proxy-auth-ix28b5 branch 2 times, most recently from 5c78170 to 2107550 Compare July 7, 2026 22:05
Allow Wave to work behind a corporate egress proxy that requires
authentication. The Java HttpClient instances created by
HttpClientFactory now accept a proxy configuration resolved from the
wave.httpclient.proxy.* settings, falling back to the
HTTPS_PROXY/HTTP_PROXY/NO_PROXY environment variables, and send the
proxy credentials via an Authenticator scoped to proxy requests
originating from the configured proxy host.

When an authenticating proxy is configured, the
jdk.http.auth.tunneling.disabledSchemes and
jdk.http.auth.proxying.disabledSchemes system properties are defaulted
to empty at bootstrap - unless already set by the operator - since the
JDK disables Basic authentication on HTTPS CONNECT tunnelling by
default.

No proxy configured means unchanged behaviour.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zJyj9FkzUpMNuH51ScqAQ
Signed-off-by: Claude <noreply@anthropic.com>
@ewels
ewels force-pushed the claude/wave-proxy-auth-ix28b5 branch from 2107550 to 2460c8b Compare July 8, 2026 20:21
@ewels

ewels commented Jul 13, 2026

Copy link
Copy Markdown
Member Author

Note that another approach here could be to pull in libseqera to do more of the work and avoid having a duplicate fix to the other linked PRs. As that's a bigger change with wider architectural decisions to make I will leave that alone for now though.

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.

2 participants