Support an authenticating egress proxy via shared lib-util-net ProxyConfig - #1125
Conversation
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>
|
@claude review |
There was a problem hiding this comment.
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@PostConstructrunning before the firstHttpClientFactoryclient is built (the staticsclient1/client2are 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@Contextbean that does real work at startup,Bootstrap, goes throughRegistryCredentialsProviderImpl→RegistryCredentialsFactoryImpl, which only touches the AWS SDK for ECR hosts and neverHttpClientFactory. So the contract holds today. jdk.http.auth.tunneling.disabledSchemestiming.jdk.internal.net.http.common.Utilssnapshots this in a static initializer, so it must be set before the firstHttpClientis built. Sinceinit()runs both the env path (setupFromEnvironment) andenableBasicProxyTunneling()before any client construction, this is fine — and the docs correctly offerJAVA_TOOL_OPTIONSas the belt-and-braces alternative.- Credential leakage. No proxy password is logged; the config log line is unchanged, and Wave exposes no
/envmanagement endpoint. Nothing else in the repo callsAuthenticator.setDefault, so the JVM-wide authenticator installed bysetupFromEnvironmentdoesn't clobber an existing one. - Only-tunneling, not proxying. Dropping #1076's
jdk.http.auth.proxying.disabledSchemesdefaulting is right — the JDK only defaults the tunneling property toBasic. - Backward compatibility. With neither setting nor env var present,
applyProxyis a no-op andProxySelector.getDefault()still applies, so-Dhttps.proxyHostdeployments are unaffected.
Non-blocking notes
-
The docs statement about the AWS SDK looks wrong.
reference.mdsays 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 noProxyConfiguration, and SDK v2's defaultProxyConfigurationhasuseSystemPropertyValues = true— so oncesetupFromEnvironmentinstallshttps.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 inNO_PROXY. Worth correcting the sentence (Micronaut declarative clients and the out-of-process build/scan/blob jobs in that same list are accurate). -
setProxyConfigno 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 invalidatingclient1/client2in the setter, or havingapplyProxypull the config lazily, would make the invariant self-enforcing rather than documented. -
proxyNoProxy?.tokenize(',')doesn't trim.no-proxy: "a.example.com, b.example.com"yields" b.example.com"with a leading space. IfProxyConfigdoesn'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. -
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:uritaking precedence over the environment, the warn-and-ignore branch whenusername/password/no-proxyare set withouturi, and the tokenizing. A small spec assertingHttpClientFactoryends up with the expectedProxyConfigwould pin the wiring. -
Trivial: the
{@link Authenticator}in theproxyConfig()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>
Summary
Adds authenticating egress-proxy support for Wave's outbound
java.net.http.HttpClientinstances (created byHttpClientFactory), 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 sharedio.seqera:lib-util-net:0.2.0(io.seqera.util.net.ProxyConfig), not in Wave.Changes
HttpClientConfigresolves the proxy from thewave.httpclient.proxy.*settings (scoped to Wave's own HTTP clients) or, from the environment, viaProxyConfig.setupFromEnvironment(System.getenv())— installing it JVM-wide (per-protocol<proto>.proxyHost/.proxyPort+http.nonProxyHostssystem properties and a defaultAuthenticator), mirroring Nextflow soHTTPS_PROXY/HTTP_PROXY/FTP_PROXY/NO_PROXYbehave identically across both.HttpClientFactoryapplies the resolved selector + authenticator to the clients it builds;jdk.http.auth.tunneling.disabledSchemesis cleared when credentials are configured (Basic over the HTTPS CONNECT tunnel).io.seqera:lib-util-net:0.2.0; no new resolver code or test double in Wave.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 inlib-util-net, and the end-to-end proxy-auth path (through a realHttpClient) 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