diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcService.java index 97342294e0..883528c2a6 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcService.java @@ -604,10 +604,11 @@ public class JsonRpcService { final Iterable splitHostHeader = Splitter.on(':').split(hostname); final long hostPieces = stream(splitHostHeader).count(); // If the host contains a colon, verify the host is correctly formed - host [ ":" port ] - if (hostPieces > 2 || !Iterables.get(splitHostHeader, 1).matches("\\d{1,5}+")) { - return Optional.empty(); + if (hostPieces > 1) { + if (hostPieces > 2 || !Iterables.get(splitHostHeader, 1).matches("\\d{1,5}+")) { + return Optional.empty(); + } } - return Optional.ofNullable(Iterables.get(splitHostHeader, 0)); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/JsonRpcJWTTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/JsonRpcJWTTest.java index 0df9e4a1ed..965e32eb3a 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/JsonRpcJWTTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/JsonRpcJWTTest.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.websocket; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.spy; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcService; @@ -44,9 +45,13 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import io.vertx.core.MultiMap; import io.vertx.core.Vertx; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientOptions; +import io.vertx.core.http.HttpClientRequest; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.http.HttpMethod; import io.vertx.core.http.WebSocket; import io.vertx.core.http.WebSocketConnectOptions; import io.vertx.core.json.Json; @@ -216,6 +221,7 @@ public class JsonRpcJWTTest { wsOpts.setURI("/"); wsOpts.addHeader( "Authorization", "Bearer " + ((EngineAuthService) jwtAuth.get()).createToken()); + wsOpts.addHeader(HttpHeaders.HOST, "anything"); final Async async = context.async(); httpClient.webSocket( @@ -246,6 +252,136 @@ public class JsonRpcJWTTest { httpClient.close(); } + @Test + public void wsRequestFromBadHostAndValidJWTIsDenied(final TestContext context) { + + JsonRpcConfiguration strictHost = JsonRpcConfiguration.createEngineDefault(); + strictHost.setHostsAllowlist(List.of("localhost")); + strictHost.setPort(0); + try { + strictHost.setAuthenticationPublicKeyFile( + new File(this.getClass().getResource("jwt.hex").toURI())); + } catch (URISyntaxException e) { + fail("didn't parse jwt"); + } + + JsonRpcService jsonRpcService = + spy( + new JsonRpcService( + vertx, + bufferDir, + strictHost, + new NoOpMetricsSystem(), + new NatService(Optional.empty(), true), + websocketMethods, + Optional.empty(), + scheduler, + jwtAuth, + healthy, + healthy)); + + jsonRpcService.start().join(); + + final InetSocketAddress inetSocketAddress = jsonRpcService.socketAddress(); + int listenPort = inetSocketAddress.getPort(); + + final HttpClientOptions httpClientOptions = + new HttpClientOptions().setDefaultHost(HOSTNAME).setDefaultPort(listenPort); + + httpClient = vertx.createHttpClient(httpClientOptions); + + WebSocketConnectOptions wsOpts = new WebSocketConnectOptions(); + wsOpts.setPort(listenPort); + wsOpts.setHost(HOSTNAME); + wsOpts.setURI("/"); + wsOpts.addHeader( + "Authorization", "Bearer " + ((EngineAuthService) jwtAuth.get()).createToken()); + wsOpts.addHeader(HttpHeaders.HOST, "bogushost"); + + final Async async = context.async(); + httpClient.webSocket( + wsOpts, + connected -> { + if (connected.failed()) { + connected.cause().printStackTrace(); + } + assertThat(connected.succeeded()).isFalse(); + async.complete(); + }); + + async.awaitSuccess(10000); + jsonRpcService.stop(); + + httpClient.close(); + } + + @Test + public void httpRequestFromBadHostAndValidJWTIsDenied(final TestContext context) { + + JsonRpcConfiguration strictHost = JsonRpcConfiguration.createEngineDefault(); + strictHost.setHostsAllowlist(List.of("localhost")); + strictHost.setPort(0); + try { + strictHost.setAuthenticationPublicKeyFile( + new File(this.getClass().getResource("jwt.hex").toURI())); + } catch (URISyntaxException e) { + fail("didn't parse jwt"); + } + + JsonRpcService jsonRpcService = + spy( + new JsonRpcService( + vertx, + bufferDir, + strictHost, + new NoOpMetricsSystem(), + new NatService(Optional.empty(), true), + websocketMethods, + Optional.empty(), + scheduler, + jwtAuth, + healthy, + healthy)); + + jsonRpcService.start().join(); + + final InetSocketAddress inetSocketAddress = jsonRpcService.socketAddress(); + int listenPort = inetSocketAddress.getPort(); + + final HttpClientOptions httpClientOptions = + new HttpClientOptions().setDefaultHost(HOSTNAME).setDefaultPort(listenPort); + + httpClient = vertx.createHttpClient(httpClientOptions); + + MultiMap headers = + HttpHeaders.set( + "Authorization", "Bearer " + ((EngineAuthService) jwtAuth.get()).createToken()) + .set(HttpHeaders.HOST, "bogushost"); + + final Async async = context.async(); + httpClient.request( + HttpMethod.GET, + "/", + connected -> { + if (connected.failed()) { + connected.cause().printStackTrace(); + } + HttpClientRequest request = connected.result(); + request.headers().addAll(headers); + request.send( + response -> { + assertThat(response.result().statusCode()).isNotEqualTo(500); + assertThat(response.result().statusCode()).isEqualTo(403); + async.complete(); + }); + }); + + async.awaitSuccess(10000); + jsonRpcService.stop(); + + httpClient.close(); + } + @Test public void httpRequestWithDefaultHeaderAndInvalidJWTIsDenied(final TestContext context) {