[ 
https://issues.apache.org/jira/browse/KNOX-3519?focusedWorklogId=1048785&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1048785
 ]

ASF GitHub Bot logged work on KNOX-3519:
----------------------------------------

                Author: ASF GitHub Bot
            Created on: 21/Oct/26 16:04
            Start Date: 21/Oct/26 16:04
    Worklog Time Spent: 10m 
      Work Description: smolnar82 commented on code in PR #1461:
URL: https://github.com/apache/knox/pull/1461#discussion_r4338902495


##########
gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/HttpClientResourceRetrieverTest.java:
##########
@@ -0,0 +1,251 @@
+/*
+ * 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.services.token.impl;
+
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.util.Resource;
+import org.apache.http.impl.client.CloseableHttpClient;
+import 
org.apache.knox.gateway.services.knoxidf.trustedoidcissuer.TestJwksHttpsServer;
+import org.apache.knox.gateway.util.MetadataHttpClients;
+import org.apache.knox.gateway.util.TruststoreSSLContextUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.net.ssl.SSLSocketFactory;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URL;
+import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+public class HttpClientResourceRetrieverTest {
+
+  private static final int SIZE_LIMIT = 51200;
+  private static final int TIMEOUT_MS = 2000;
+
+  private TestJwksHttpsServer server;
+  private URL jwksUrl;
+  private SSLSocketFactory trustingFactory;
+
+  @Before
+  public void setUp() throws Exception {
+    server = TestJwksHttpsServer.start();
+    jwksUrl = URI.create(server.getJwksUrl()).toURL();
+    final KeyStore truststore = 
KeyStore.getInstance(KeyStore.getDefaultType());
+    truststore.load(null, null);
+    truststore.setCertificateEntry("ca", server.getCertificate());
+    trustingFactory = 
TruststoreSSLContextUtils.getTruststoreSSLContext(truststore).getSocketFactory();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    if (server != null) {
+      server.close();
+    }
+  }
+
+  @Test
+  public void testRetrievesTheKeySet() throws Exception {
+    try (CloseableHttpClient client = clientTrusting(trustingFactory)) {
+      final Resource resource = new HttpClientResourceRetriever(client, 
SIZE_LIMIT).retrieveResource(jwksUrl);
+
+      assertEquals(1, JWKSet.parse(resource.getContent()).getKeys().size());
+    }
+  }
+
+  /*
+   * The reason this retriever exists. BouncyCastle's JSSE takes the hostname 
for HTTPS endpoint
+   * identification from the socket; HttpsURLConnection, which Nimbus uses by 
default, creates the
+   * socket with no host and so the check fails before the certificate is 
examined. Apache
+   * HttpClient layers TLS over a connected socket and hands it the target 
host, which is what
+   * makes endpoint identification work for every provider without any special 
casing.
+   */
+  @Test
+  public void testSuppliesTheTargetHostToTheSocket() throws Exception {
+    final HostRecordingSocketFactory recording = new 
HostRecordingSocketFactory(trustingFactory);
+
+    try (CloseableHttpClient client = clientTrusting(recording)) {
+      new HttpClientResourceRetriever(client, 
SIZE_LIMIT).retrieveResource(jwksUrl);
+    }
+
+    /*
+     * Only the layered call is assertable: HttpClient creates the underlying 
plain socket through
+     * its own connection factory, never through this one, so a "nothing 
hostless was created"
+     * assertion here would hold no matter what the production code did.
+     */
+    assertEquals("the target host must reach the socket, or BouncyCastle 
cannot identify the endpoint",
+        List.of("localhost"), recording.layeredHosts);
+  }
+
+  /*
+   * The host is not fixed at construction: it comes from the URL of each 
request, so a JWKS
+   * endpoint named by IP literal gets that IP and one named by DNS gets that 
name. Requests the
+   * same server by a second spelling to show the recorded host tracks the URL 
rather than being
+   * whatever the first request happened to use.
+   */
+  @Test
+  public void testTheHostComesFromTheRequestedUrl() throws Exception {
+    final HostRecordingSocketFactory recording = new 
HostRecordingSocketFactory(trustingFactory);
+    /*
+     * The address "localhost" resolves to on this host, which is the one 
Jetty bound - assuming
+     * 127.0.0.1 would fail at connect wherever localhost is ::1.
+     */
+    final String loopbackIp = 
InetAddress.getByName("localhost").getHostAddress();
+    final URL byLoopbackIp =
+        URI.create("https://"; + hostForUrl(loopbackIp) + ":" + 
jwksUrl.getPort() + jwksUrl.getPath()).toURL();
+
+    try (CloseableHttpClient client = clientTrusting(recording)) {
+      final HttpClientResourceRetriever retriever = new 
HttpClientResourceRetriever(client, SIZE_LIMIT);
+      retriever.retrieveResource(jwksUrl);
+      try {
+        /*
+         * The certificate is issued for localhost, so this spelling may fail 
verification - which
+         * is correct, and beside the point: the host reaches the socket 
either way.
+         */
+        retriever.retrieveResource(byLoopbackIp);
+      } catch (IOException expectedForAnUnmatchedName) { // NOPMD - asserted 
on the record below
+        /* ignored */

Review Comment:
   The k8s case, an IP URL with an IP-SAN cert, is never shown to succeed. 
Please add a positive test: a cert with an iPAddress SAN for loopback, fetched 
via `https://127.0.0.1:<port>/jwks`. Optionally add a BCJSSE run 
(bctls-jdk18on, test scope) to reproduce the original failure.





Issue Time Tracking
-------------------

    Worklog Id:     (was: 1048785)
    Time Spent: 2h 10m  (was: 2h)

> Calls to JWKS enndpoints 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 10m
>  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)

Reply via email to