[
https://issues.apache.org/jira/browse/KNOX-3519?focusedWorklogId=1048788&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1048788
]
ASF GitHub Bot logged work on KNOX-3519:
----------------------------------------
Author: ASF GitHub Bot
Created on: 21/Oct/26 16:11
Start Date: 21/Oct/26 16:11
Worklog Time Spent: 10m
Work Description: smolnar82 commented on code in PR #1461:
URL: https://github.com/apache/knox/pull/1461#discussion_r4338884613
##########
gateway-server/src/main/java/org/apache/knox/gateway/util/MetadataHttpClients.java:
##########
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
under
+ * the License.
+ */
+package org.apache.knox.gateway.util;
+
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Builds the HTTP clients used to fetch identity-provider metadata - OIDC
discovery documents and
+ * JWK sets. Both are small, idempotent JSON GETs over TLS, so they want the
same configuration.
+ * <p>
+ * The number of in-client retries is a parameter rather than a constant: a
caller with retry
+ * layers above it wants {@link #NO_RETRIES}, while one that is the only layer
wants
+ * {@link #DEFAULT_RETRIES}.
+ * <p>
+ * Apache HttpClient is used rather than {@link java.net.HttpURLConnection}
because it
+ * layers TLS over an already-connected socket and hands it the target host,
so a
+ * JSSE provider that takes the endpoint identification hostname from the
socket, BouncyCastle's
+ * FIPS provider among them has a hostname to check. {@code HttpURLConnection}
creates its socket
+ * without one.
+ */
+public final class MetadataHttpClients {
+
+ /** No retries inside the client, for callers that already retry around it.
*/
+ public static final int NO_RETRIES = 0;
+
+ /** Retries for callers that are the only retry layer. */
+ public static final int DEFAULT_RETRIES = 2;
+
+ /*
+ * The pool defaults - 2 per route - are far too small to share with a lease
wait, so both are
+ * set explicitly and the wait is bounded below.
+ */
+ private static final int CONNECTION_LEASE_TIMEOUT_MS = 2000;
+
+ private static final int MAX_CONNECTIONS_PER_ROUTE = 10;
+ private static final int MAX_CONNECTIONS_TOTAL = 40;
+
+ /*
+ * Idle connections in the pool are closed after this long, so the next
fetch goes through a
+ * fresh connection rather than one the remote end or a middlebox has
silently closed.
+ */
+ private static final long IDLE_EVICTION_SECONDS = 60L;
+
+ private MetadataHttpClients() {
+ }
+
+ /**
+ * A client intended to be held for the gateway's lifetime, with idle and
expired connections
+ * evicted from its pool. The caller is responsible for closing it.
+ */
+ public static CloseableHttpClient pooled(int connectTimeoutMs, int
readTimeoutMs, int retries) {
+ return builder(connectTimeoutMs, readTimeoutMs, retries)
+ .evictIdleConnections(IDLE_EVICTION_SECONDS, TimeUnit.SECONDS)
+ .evictExpiredConnections()
+ .build();
+ }
+
+ /**
+ * A short-lived client that trusts only the given SSL context, for one
fetch. Hostname
+ * verification stays on, and it holds no eviction thread because the caller
closes it promptly.
+ */
+ public static CloseableHttpClient trusting(int connectTimeoutMs, int
readTimeoutMs, SSLContext sslContext,
+ int retries) {
+ return builder(connectTimeoutMs, readTimeoutMs, retries)
+ .setSSLContext(sslContext)
+ .build();
+ }
+
+ /**
+ * As {@link #trusting(int, int, SSLContext, int)}, for callers that already
hold the socket factory
+ * rather than the context it came from.
+ */
+ public static CloseableHttpClient trusting(int connectTimeoutMs, int
readTimeoutMs, SSLSocketFactory socketFactory,
+ int retries) {
+ return builder(connectTimeoutMs, readTimeoutMs, retries)
+ .setSSLSocketFactory(new SSLConnectionSocketFactory(socketFactory,
+ SSLConnectionSocketFactory.getDefaultHostnameVerifier()))
+ .build();
+ }
+
+ /*
+ * requestSentRetryEnabled=true: these endpoints are GET-only and so
idempotent (RFC 7231
+ * 4.2.2), which makes retrying after the request was sent safe and covers
the common failure -
+ * a connection reset mid-response.
+ */
+ private static HttpClientBuilder builder(int connectTimeoutMs, int
readTimeoutMs, int retries) {
+ final RequestConfig requestConfig = RequestConfig.custom()
+ .setConnectTimeout(connectTimeoutMs)
+ .setSocketTimeout(readTimeoutMs)
+ /*
+ * A separate, non-zero value: without a lease timeout the wait for a
pooled connection is
+ * unbounded however short the other two are, and reusing the connect
timeout would inherit
+ * a configured zero and mean "wait for ever" again.
+ */
+ .setConnectionRequestTimeout(CONNECTION_LEASE_TIMEOUT_MS)
+ .build();
+ return HttpClients.custom()
+ .setDefaultRequestConfig(requestConfig)
+ /*
+ * Honour https.protocols / https.cipherSuites, which the
HttpURLConnection path these
+ * clients replace applied automatically. An explicitly supplied SSL
context still wins.
+ */
+ .useSystemProperties()
Review Comment:
OIDC discovery now picks up the JVM proxy settings (`https.proxyHost`,
`http.nonProxyHosts`), which it didn't before. It matches the JWKS path now,
which is fine, but we may want to document it.
Issue Time Tracking
-------------------
Worklog Id: (was: 1048788)
Time Spent: 2h 40m (was: 2.5h)
> Calls to JWKS endpoints with ip address are failing
> ---------------------------------------------------
>
> Key: KNOX-3519
> URL: https://issues.apache.org/jira/browse/KNOX-3519
> Project: Apache Knox
> Issue Type: Bug
> Reporter: Sandeep More
> Assignee: Sandeep More
> Priority: Major
> Time Spent: 2h 40m
> Remaining Estimate: 0h
>
> Calls to JWKS endpoint with ip address fail e.g.
> https://172.18.0.2:6443/openid/v1/jwks
> Stack trace:
> {code:java}
> 2026-10-19 23:15:18,262 42f8a538-720e-4467-ac21-e71c95f6f9c3 ERROR
> token.state (DefaultTokenAuthorityService.java:verifyToken(371)) - Failed to
> verify token using JWKS endpoint https://10.83.6.18:6443/openid/v1/jwks,
> reason: com.nimbusds.jose.jwk.source.JWKSetRetrievalException: Couldn't
> retrieve JWK set from URL: certificate_unknown(46)
> org.apache.knox.gateway.services.security.token.TokenServiceException: Cannot
> verify token.
> at
> org.apache.knox.gateway.services.token.impl.DefaultTokenAuthorityService.verifyToken(DefaultTokenAuthorityService.java:331)
> at
> org.apache.knox.gateway.services.token.impl.DefaultTokenAuthorityService.verifyToken(DefaultTokenAuthorityService.java:365)
> at
> org.apache.knox.gateway.provider.federation.jwt.filter.AbstractJWTFilter.verifyTokenSignature(AbstractJWTFilter.java:966)
> at
> org.apache.knox.gateway.provider.federation.jwt.filter.AbstractJWTFilter.doFullTokenValidation(AbstractJWTFilter.java:765)
> at
> org.apache.knox.gateway.provider.federation.jwt.filter.AbstractJWTFilter.validateToken(AbstractJWTFilter.java:683)
> at
> org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter.parseAndValidateJWT(JWTFederationFilter.java:733)
> at
> org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter.parseAndValidateJWT(JWTFederationFilter.java:720)
> at
> org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter.doFilter(JWTFederationFilter.java:346)
> at
> org.apache.knox.gateway.GatewayFilter$Holder.doFilter(GatewayFilter.java:391)
> at
> org.apache.knox.gateway.GatewayFilter$Chain.doFilter(GatewayFilter.java:305)
> at
> org.apache.knox.gateway.filter.XForwardedHeaderFilter.doFilter(XForwardedHeaderFilter.java:50)
> at
> org.apache.knox.gateway.filter.AbstractGatewayFilter.doFilter(AbstractGatewayFilter.java:59)
> at
> org.apache.knox.gateway.GatewayFilter$Holder.doFilter(GatewayFilter.java:391)
> at
> org.apache.knox.gateway.GatewayFilter$Chain.doFilter(GatewayFilter.java:305)
> at
> org.apache.knox.gateway.GatewayFilter.doFilter(GatewayFilter.java:191)
> at
> org.apache.knox.gateway.GatewayFilter.doFilter(GatewayFilter.java:102)
> at
> org.apache.knox.gateway.GatewayServlet.service(GatewayServlet.java:127)
> at
> org.eclipse.jetty.ee10.servlet.ServletHolder$NotAsync.service(ServletHolder.java:1387)
> at
> org.eclipse.jetty.ee10.servlet.ServletHolder.handle(ServletHolder.java:753)
> at
> org.eclipse.jetty.ee10.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1624)
> at
> org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:195)
> at
> org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
> at
> org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1596)
> at
> org.eclipse.jetty.ee10.servlet.ServletHandler$MappedServlet.handle(ServletHandler.java:1557)
> at
> org.eclipse.jetty.ee10.servlet.ServletChannel.dispatch(ServletChannel.java:828)
> at
> org.eclipse.jetty.ee10.servlet.ServletChannel.handle(ServletChannel.java:438)
> at
> org.eclipse.jetty.ee10.servlet.ServletHandler.handle(ServletHandler.java:470)
> at
> org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:575)
> at
> org.eclipse.jetty.ee10.servlet.SessionHandler.handle(SessionHandler.java:717)
> at
> org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:1102)
> at
> org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:181)
> at org.eclipse.jetty.server.Handler$Wrapper.handle(Handler.java:740)
> at
> org.apache.knox.gateway.trace.TraceHandler.handle(TraceHandler.java:72)
> at org.eclipse.jetty.server.Handler$Wrapper.handle(Handler.java:740)
> at
> org.apache.knox.gateway.filter.CorrelationHandler.handle(CorrelationHandler.java:50)
> at org.eclipse.jetty.server.Handler$Wrapper.handle(Handler.java:740)
> at
> org.apache.knox.gateway.filter.PortMappingHelperHandler.handle(PortMappingHelperHandler.java:104)
> at org.eclipse.jetty.server.Server.handle(Server.java:182)
> at
> org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:772)
> at
> org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:415)
> at
> org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:323)
> at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
> at
> org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
> at
> org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:492)
> at
> org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.epcRunTask(AdaptiveExecutionStrategy.java:428)
> at
> org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:401)
> at
> org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:255)
> at
> org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.run(AdaptiveExecutionStrategy.java:204)
> at
> org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:311)
> at
> org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:981)
> at
> org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1212)
> at
> org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1166)
> at java.base/java.lang.Thread.run(Thread.java:840)
> Caused by: com.nimbusds.jose.jwk.source.JWKSetRetrievalException: Couldn't
> retrieve JWK set from URL: certificate_unknown(46)
> at
> com.nimbusds.jose.jwk.source.URLBasedJWKSetSource.getJWKSet(URLBasedJWKSetSource.java:92)
> at
> com.nimbusds.jose.jwk.source.RetryingJWKSetSource.getJWKSet(RetryingJWKSetSource.java:99)
> at
> com.nimbusds.jose.jwk.source.OutageTolerantJWKSetSource.getJWKSet(OutageTolerantJWKSetSource.java:113)
> at
> com.nimbusds.jose.jwk.source.RateLimitedJWKSetSource.getJWKSet(RateLimitedJWKSetSource.java:107)
> at
> com.nimbusds.jose.jwk.source.CachingJWKSetSource.loadJWKSetNotThreadSafe(CachingJWKSetSource.java:330)
> at
> com.nimbusds.jose.jwk.source.RefreshAheadCachingJWKSetSource.loadJWKSetNotThreadSafe(RefreshAheadCachingJWKSetSource.java:347)
> at
> com.nimbusds.jose.jwk.source.CachingJWKSetSource.loadJWKSetBlocking(CachingJWKSetSource.java:240)
> at
> com.nimbusds.jose.jwk.source.RefreshAheadCachingJWKSetSource.getJWKSet(RefreshAheadCachingJWKSetSource.java:326)
> at
> com.nimbusds.jose.jwk.source.JWKSetBasedJWKSource.get(JWKSetBasedJWKSource.java:76)
> at
> com.nimbusds.jose.proc.JWSVerificationKeySelector.selectJWSKeys(JWSVerificationKeySelector.java:150)
> at
> com.nimbusds.jwt.proc.DefaultJWTProcessor.selectKeys(DefaultJWTProcessor.java:320)
> at
> com.nimbusds.jwt.proc.DefaultJWTProcessor.process(DefaultJWTProcessor.java:391)
> at
> com.nimbusds.jwt.proc.DefaultJWTProcessor.process(DefaultJWTProcessor.java:340)
> at
> com.nimbusds.jwt.proc.DefaultJWTProcessor.process(DefaultJWTProcessor.java:331)
> at
> org.apache.knox.gateway.services.token.impl.DefaultTokenAuthorityService.verifyToken(DefaultTokenAuthorityService.java:319)
> ... 52 more
> Caused by: org.bouncycastle.tls.TlsFatalAlert: certificate_unknown(46)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.jsse.provider.ProvSSLSocketDirect.checkServerTrusted(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.jsse.provider.ProvTlsClient$1.notifyServerCertificate(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.tls.TlsUtils.processServerCertificate(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.tls.TlsClientProtocol.handleServerCertificate(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.tls.TlsClientProtocol.receive13ServerCertificate(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.tls.TlsClientProtocol.handle13HandshakeMessage(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.tls.TlsClientProtocol.handleHandshakeMessage(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.jsse.provider.ProvTlsClientProtocol.handleHandshakeMessage(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.tls.TlsProtocol.processHandshakeQueue(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.tls.TlsProtocol.processRecord(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.tls.RecordStream.readRecord(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.tls.TlsProtocol.safeReadRecord(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.tls.TlsProtocol.blockForHandshake(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.tls.TlsClientProtocol.connect(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.jsse.provider.ProvSSLSocketDirect.startHandshake(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.jsse.provider.ProvSSLSocketDirect.startHandshake(Unknown
> Source)
> at
> java.base/sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:596)
> at
> java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:190)
> at
> java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1719)
> at
> java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1644)
> at
> java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:224)
> at
> com.nimbusds.jose.util.DefaultResourceRetriever.getInputStream(DefaultResourceRetriever.java:366)
> at
> com.nimbusds.jose.util.DefaultResourceRetriever.retrieveResource(DefaultResourceRetriever.java:269)
> at
> com.nimbusds.jose.jwk.source.URLBasedJWKSetSource.getJWKSet(URLBasedJWKSetSource.java:90)
> ... 66 more
> Caused by: java.security.cert.CertificateException: No hostname specified for
> HTTPS endpoint ID check
> at
> org.bouncycastle.fips.tls/org.bouncycastle.jsse.provider.HostnameUtil.checkHostname(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.jsse.provider.ProvX509TrustManager.checkEndpointID(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.jsse.provider.ProvX509TrustManager.checkEndpointID(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.jsse.provider.ProvX509TrustManager.checkExtendedTrust(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.jsse.provider.ProvX509TrustManager.checkTrusted(Unknown
> Source)
> at
> org.bouncycastle.fips.tls/org.bouncycastle.jsse.provider.ProvX509TrustManager.checkServerTrusted(Unknown
> Source)
> ... 90 more {code}
>
--
This message was sent by Atlassian Jira
(v8.20.10#820010)