Skip to content

Support an authenticating egress proxy via shared lib-util-net ProxyConfig - #1125

Merged
pditommaso merged 2 commits into
masterfrom
claude/wave-egress-proxy-shared
Sep 8, 2026
Merged

pditommaso merged 2 commits into
masterfrom
claude/wave-egress-proxy-shared

Conversation

@pditommaso

Copy link
Copy Markdown
Collaborator

Summary

Adds authenticating egress-proxy support for Wave's outbound java.net.http.HttpClient instances (created by HttpClientFactory), so an on-prem Wave behind a corporate proxy can reach container registries and Seqera Platform. +109/−7 — the proxy resolution itself lives in the shared io.seqera:lib-util-net:0.2.0 (io.seqera.util.net.ProxyConfig), not in Wave.

This is a leaner alternative to #1076: it addresses the same nextflow-io/nextflow#7305 but consumes the shared resolver instead of a Wave-local one, so Wave adds no ProxyConfig/test-double of its own and no lib-httpx dependency. Opening it as a separate PR rather than rewriting #1076.

Changes

  • HttpClientConfig resolves the proxy from the wave.httpclient.proxy.* settings (scoped to Wave's own HTTP clients) or, from the environment, via ProxyConfig.setupFromEnvironment(System.getenv()) — installing it JVM-wide (per-protocol <proto>.proxyHost/.proxyPort + http.nonProxyHosts system properties and a default Authenticator), mirroring Nextflow so HTTPS_PROXY/HTTP_PROXY/FTP_PROXY/NO_PROXY behave identically across both.
  • HttpClientFactory applies the resolved selector + authenticator to the clients it builds; jdk.http.auth.tunneling.disabledSchemes is cleared when credentials are configured (Basic over the HTTPS CONNECT tunnel).
  • Adds io.seqera:lib-util-net:0.2.0; no new resolver code or test double in Wave.
  • Docs: new Egress proxy section in docs/install/reference.md.

Where the logic and tests live

io.seqera.util.net.ProxyConfig (parsing, credential decoding, no-proxy matching, proxy-scoped authenticator, JVM-global setup) is unit-tested in lib-util-net, and the end-to-end proxy-auth path (through a real HttpClient) is covered by lib-httpx's integration test. Shipped in seqeralabs/libseqera#122 (lib-util-net 0.2.0) and #123 (lib-httpx 2.7.0), both merged and published. Same consolidation applied in nextflow-io/nextflow#7587.

🤖 Generated with Claude Code

Route Wave's outbound java.net.http.HttpClient instances (created by HttpClientFactory)
through a corporate egress proxy, including an authenticating one, resolved via the shared
io.seqera.util.net.ProxyConfig from io.seqera:lib-util-net:0.2.0.

HttpClientConfig resolves the proxy from the wave.httpclient.proxy.* settings (scoped to
Wave's own HTTP clients) or falls back to the HTTPS_PROXY/HTTP_PROXY/NO_PROXY environment
variables, which - like Nextflow - are installed JVM-wide via ProxyConfig.setupFromEnvironment
(per-protocol proxyHost/proxyPort + http.nonProxyHosts system properties and a default
proxy Authenticator). HttpClientFactory applies the resolved selector + authenticator to its
clients. Basic auth over the HTTPS CONNECT tunnel is enabled by clearing
jdk.http.auth.tunneling.disabledSchemes when credentials are configured.

The URI/env parsing, no-proxy matching and authenticator semantics live in lib-util-net
(shared with Nextflow and lib-httpx), so Wave carries no proxy resolver of its own.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@pditommaso

Copy link
Copy Markdown
Collaborator Author

@claude review

@pditommaso
pditommaso requested a review from ewels September 7, 2026 09:37

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approve — clean, surgical wiring of the shared lib-util-net proxy resolver into Wave's single outbound-client chokepoint, with no correctness or security issue found. Notes below are non-blocking.

I read the diff, HttpClientFactory, HttpClientConfig, every HttpClientFactory.* call site, the @Context beans, and the AWS/S3/ECR client factories. Compared against the superseded #1076 to see what was dropped.

What I checked

  • Init ordering. The change hinges on HttpClientConfig's @Context @PostConstruct running before the first HttpClientFactory client is built (the statics client1/client2 are cached forever). I walked all nine call sites — RegistryProxyService.client(), RegistryAuthServiceImpl (×2), RegistryLookupServiceImpl, StreamServiceImpl, ContainerInspectServiceImpl, ManifestAssembler, HttpTowerConnector — and every one is inside a request-time method body, none in a constructor or @PostConstruct. The one @Context bean that does real work at startup, Bootstrap, goes through RegistryCredentialsProviderImplRegistryCredentialsFactoryImpl, which only touches the AWS SDK for ECR hosts and never HttpClientFactory. So the contract holds today.
  • jdk.http.auth.tunneling.disabledSchemes timing. jdk.internal.net.http.common.Utils snapshots this in a static initializer, so it must be set before the first HttpClient is built. Since init() runs both the env path (setupFromEnvironment) and enableBasicProxyTunneling() before any client construction, this is fine — and the docs correctly offer JAVA_TOOL_OPTIONS as the belt-and-braces alternative.
  • Credential leakage. No proxy password is logged; the config log line is unchanged, and Wave exposes no /env management endpoint. Nothing else in the repo calls Authenticator.setDefault, so the JVM-wide authenticator installed by setupFromEnvironment doesn't clobber an existing one.
  • Only-tunneling, not proxying. Dropping #1076's jdk.http.auth.proxying.disabledSchemes defaulting is right — the JDK only defaults the tunneling property to Basic.
  • Backward compatibility. With neither setting nor env var present, applyProxy is a no-op and ProxySelector.getDefault() still applies, so -Dhttps.proxyHost deployments are unaffected.

Non-blocking notes

  1. The docs statement about the AWS SDK looks wrong. reference.md says the env-var path is honoured JVM-wide but that "the AWS SDK clients such as ECR and S3 … keep their own proxy configuration regardless". Wave builds those clients bare (S3ClientFactory.defaultS3Client(), cloudflareS3Client(), AwsEcrService.ecrClient()) with no ProxyConfiguration, and SDK v2's default ProxyConfiguration has useSystemPropertyValues = true — so once setupFromEnvironment installs https.proxyHost/http.nonProxyHosts, S3 and ECR traffic will go through the proxy too. That's probably the behaviour an operator wants, but the doc currently promises the opposite, and an operator who reads it won't think to put their S3 endpoint in NO_PROXY. Worth correcting the sentence (Micronaut declarative clients and the out-of-process build/scan/blob jobs in that same list are accurate).

  2. setProxyConfig no longer invalidates the cached clients, which #1076 did. The javadoc states the "before any client is built" precondition, and as verified above it holds — but there's no guard, so a future startup-time HTTP call would silently get an unproxied client that is then cached for the process lifetime. Either invalidating client1/client2 in the setter, or having applyProxy pull the config lazily, would make the invariant self-enforcing rather than documented.

  3. proxyNoProxy?.tokenize(',') doesn't trim. no-proxy: "a.example.com, b.example.com" yields " b.example.com" with a leading space. If ProxyConfig doesn't trim entries itself, that host silently never matches. *.tokenize(',')*.trim() would remove the doubt — and the docs advertise the value as "comma-separated", which people will write with spaces.

  4. No Wave-side test for the resolution/wiring layer. The rationale (parsing, no-proxy matching and authenticator scoping are unit-tested in lib-util-net) is sound and I'm not asking for the 44 tests back. What's untested here is the thin Wave layer: uri taking precedence over the environment, the warn-and-ignore branch when username/password/no-proxy are set without uri, and the tokenizing. A small spec asserting HttpClientFactory ends up with the expected ProxyConfig would pin the wiring.

  5. Trivial: the {@link Authenticator} in the proxyConfig() groovydoc has no matching import, so it won't resolve.

Coverage I don't have

I could not inspect io.seqera:lib-util-net:0.2.0 — the artifact isn't in this shallow checkout's caches and seqeralabs/libseqera isn't reachable from here. So I'm taking on trust, from the linked (merged) libseqera PRs and the passing Build Wave (25) job, that ProxyConfig.fromUri/setupFromEnvironment/toAuthenticator behave as described — in particular that the authenticator is scoped to RequestorType.PROXY and the configured proxy host/port, which is the security-relevant property here, and that enableBasicProxyTunneling() really does respect an operator-provided value of the property as the docs claim. Those two are the assumptions I'd most want a second pair of eyes on if they haven't already been reviewed on the library side.

Address non-blocking PR review notes:
- reference.md: the AWS SDK clients (ECR, S3) are built without an
  explicit ProxyConfiguration and honour the JVM proxy system
  properties, so the env-var path proxies their traffic too — the docs
  previously stated the opposite. Note that must-bypass endpoints should
  be added to NO_PROXY.
- HttpClientConfig: add the missing java.net.Authenticator import so the
  {@link Authenticator} groovydoc reference resolves.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@pditommaso
pditommaso merged commit a992c70 into master Sep 8, 2026
3 checks passed
@pditommaso
pditommaso deleted the claude/wave-egress-proxy-shared branch September 8, 2026 18:49
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