From d423e7c0929f03e38ee4d4bfdac41bb39ecc9ec1 Mon Sep 17 00:00:00 2001 From: Alexander Twrdik <6052859+DiCanio@users.noreply.github.com> Date: Tue, 5 Aug 2025 10:43:21 +0200 Subject: [PATCH 1/4] Add Proxy Configuration Options Adds options for configuring forward proxy functionality using HTTP (SOCKS is not supported by these changes). Allows for setting the following options: - hostname - port - username - password (via file reference) - whitelist It comes with support for hostname whitelisting, effectively bypassing the proxy if the requested host matches a pattern (given as a Java RegEx). These changes contain 2 different proxy configuration paths which are necessary since the HTTP client used throughout the application is different from the one used exclusively for socket.io connections. The configuration paths provide the same functionality but have to deal with different types of clients that are configured in different ways. --- README.MD | 37 +++++---- .../common/CommonSpringConfig.java | 78 ++++++++++++++++- .../common/CommonSslSpringConfig.java | 11 --- .../message/MessageSpringConfig.java | 83 +++++++++++++++++-- src/main/resources/application.yml | 6 ++ 5 files changed, 176 insertions(+), 39 deletions(-) diff --git a/README.MD b/README.MD index 81c3120..2e601cc 100644 --- a/README.MD +++ b/README.MD @@ -27,22 +27,27 @@ releases. For the time being use your own instance or a public one._ This application requires [Maven](https://maven.apache.org) and at least Java 21 to run. Use the following environment variables to adjust the application to your needs: -| EnvVar | Description | Default | -|----------------------------------------|-----------------------------------------------------------------------------------------------|-----------------| -| AUTH_JWKS_URL | URL to retrieve a JWKS for verifying JWTs. | | -| HUB_AUTH_BASE_URL | Base URL to reach the Hub's core component. | | -| HUB_AUTH_ROBOT_ID | Robot ID associated with the node. | | -| HUB_AUTH_ROBOT_SECRET_FILE | Path to the file containing the secret of the node's associated robot account, as plain text. | | -| HUB_BASE_URL | Base URL to reach the Hub's auth component. | | -| HUB_MESSENGER_BASE_URL | Base URL to reach the Hub's messenger component. | | -| LOG_LEVEL | Log level being used. Can be either of `trace`, `debug`, `info`, `warn` or `error`. | `info` | -| MANAGEMENT_SERVER_PORT | Port being used by the management server (providing health check endpoints etc.) | `8090` | -| PERSISTENCE_DATABASE_NAME | Database name to use when connecting to a MongoDB instance. | `messagebroker` | -| PERSISTENCE_HOSTNAME | Hostname to use to connect to a MongoDB instance. | `localhost` | -| PERSISTENCE_PORT | Port to use to connect to a MongoDB instance. | `17017` | -| SECURITY_ADDITIONAL_TRUSTED_CERTS_FILE | Path to a certificate bundle containing additional certificates to be loaded during startup. | | -| SECURITY_NODE_PRIVATE_ECDH_KEY_FILE | Path to the file containing the node's private EC key in PEM format, as plain text. | | -| SERVER_PORT | Port being used by the Web server. | `8080` | +| EnvVar | Description | Default | +|----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------| +| AUTH_JWKS_URL | URL to retrieve a JWKS for verifying JWTs. | | +| HUB_AUTH_BASE_URL | Base URL to reach the Hub's core component. | | +| HUB_AUTH_ROBOT_ID | Robot ID associated with the node. | | +| HUB_AUTH_ROBOT_SECRET_FILE | Path to the file containing the secret of the node's associated robot account, as plain text. | | +| HUB_BASE_URL | Base URL to reach the Hub's auth component. | | +| HUB_MESSENGER_BASE_URL | Base URL to reach the Hub's messenger component. | | +| LOG_LEVEL | Log level being used. Can be either of `trace`, `debug`, `info`, `warn` or `error`. | `info` | +| MANAGEMENT_SERVER_PORT | Port being used by the management server (providing health check endpoints etc.) | `8090` | +| PERSISTENCE_DATABASE_NAME | Database name to use when connecting to a MongoDB instance. | `messagebroker` | +| PERSISTENCE_HOSTNAME | Hostname to use to connect to a MongoDB instance. | `localhost` | +| PERSISTENCE_PORT | Port to use to connect to a MongoDB instance. | `17017` | +| PROXY_HOST | FQDN of the proxy to use. | | +| PROXY_PORT | Port of the proxy to use. | | +| PROXY_WHITELIST | A regex pattern (Java) to describe hosts that bypass the proxy to be reached directly. See [JavaDocs](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) for more information about the pattern usage. | | +| PROXY_USERNAME | Username being used when authenticating against the proxy. | | +| PROXY_PASSWORD_FILE | Path to the file containing the password used when authenticating against the proxy. | | +| SECURITY_ADDITIONAL_TRUSTED_CERTS_FILE | Path to a certificate bundle containing additional certificates to be loaded during startup. | | +| SECURITY_NODE_PRIVATE_ECDH_KEY_FILE | Path to the file containing the node's private EC key in PEM format, as plain text. | | +| SERVER_PORT | Port being used by the Web server. | `8080` | ## Endpoint Documentation diff --git a/src/main/java/de/privateaim/node_message_broker/common/CommonSpringConfig.java b/src/main/java/de/privateaim/node_message_broker/common/CommonSpringConfig.java index 4e231d8..7a4b3b3 100644 --- a/src/main/java/de/privateaim/node_message_broker/common/CommonSpringConfig.java +++ b/src/main/java/de/privateaim/node_message_broker/common/CommonSpringConfig.java @@ -6,6 +6,8 @@ import de.privateaim.node_message_broker.common.hub.HttpHubClient; import de.privateaim.node_message_broker.common.hub.HubClient; import de.privateaim.node_message_broker.common.hub.auth.HubOIDCAuthenticator; +import io.netty.handler.ssl.SslContext; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -15,10 +17,15 @@ import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.util.DefaultUriBuilderFactory; +import reactor.netty.http.client.HttpClient; +import reactor.netty.transport.ProxyProvider; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.List; +@Slf4j @Configuration public class CommonSpringConfig { @@ -37,6 +44,21 @@ public class CommonSpringConfig { @Value("${app.hub.auth.robotSecretFile}") private String hubAuthRobotSecretFile; + @Value("${app.proxy.host}") + private String proxyHost; + + @Value("${app.proxy.port}") + private Integer proxyPort; + + @Value("${app.proxy.whitelist}") + private String proxyWhitelist; + + @Value("${app.proxy.username}") + private String proxyUsername; + + @Value("${app.proxy.passwordFile}") + private String proxyPasswordFile; + @Qualifier("HUB_AUTH_ROBOT_SECRET") @Bean public String hubAuthRobotSecret() throws IOException { @@ -55,11 +77,59 @@ HttpRetryConfig exchangeRetryConfig() { return new HttpRetryConfig(EXCHANGE__MAX_RETRIES, EXCHANGE__MAX_RETRY_DELAY_MS); } + @Qualifier("CORE_HTTP_CLIENT") + @Bean + HttpClient decoratedHttpClient(@Qualifier("COMMON_NETTY_SSL_CONTEXT") SslContext sslContext) { + var client = HttpClient.create(); + decorateClientWithSSLContext(client, sslContext); + decorateClientWithProxySettings(client); + + return client; + } + + private void decorateClientWithSSLContext(HttpClient client, SslContext sslContext) { + client.secure(t -> t.sslContext(sslContext)); + } + + private void decorateClientWithProxySettings(HttpClient client) { + if (proxyHost != null && proxyPort != null) { + log.info("configuring usage of proxy at `{}:{}` with the following hosts whitelisted (via regex): `{}`", + proxyHost, proxyPort, proxyWhitelist); + client.proxy(proxy -> { + var proxyBuilder = proxy.type(ProxyProvider.Proxy.HTTP) + .host(proxyHost) + .port(proxyPort) + .nonProxyHosts(proxyWhitelist); + + if (proxyUsername != null && proxyPasswordFile != null) { + try { + log.info("configuring authentication for proxy"); + var proxyPassword = Files.readString(Paths.get(proxyPasswordFile)); + proxyBuilder.username(proxyUsername) + .password((_username) -> proxyPassword); + } catch (IOException e) { + log.error("cannot read password file for proxy at `{}`", proxyPasswordFile, e); + throw new RuntimeException(e); + } + } + } + ); + } else { + log.info("skipping proxy configuration due to no specified settings"); + } + } + + @Qualifier("CORE_HTTP_CONNECTOR") + @Bean + ReactorClientHttpConnector httpConnector(@Qualifier("CORE_HTTP_CLIENT") HttpClient httpClient) { + return new ReactorClientHttpConnector(httpClient); + } + @Qualifier("HUB_CORE_WEB_CLIENT") @Bean public WebClient alwaysReAuthenticatedWebClient( @Qualifier("HUB_AUTHENTICATION_MIDDLEWARE") ExchangeFilterFunction authenticationMiddleware, - @Qualifier("BASE_SSL_HTTP_CLIENT_CONNECTOR") ReactorClientHttpConnector baseSslHttpClientConnector) { + @Qualifier("CORE_HTTP_CONNECTOR") ReactorClientHttpConnector httpConnector) { // We can't use Spring's default security mechanisms out-of-the-box here since HUB uses a non-standard grant // type which is not supported. There's a way by using a custom grant type accompanied by a client manager. // However, this endeavour is not pursued for the sake of simplicity. @@ -73,7 +143,7 @@ public WebClient alwaysReAuthenticatedWebClient( .uriBuilderFactory(factory) .defaultHeaders(httpHeaders -> httpHeaders.setAccept(List.of(MediaType.APPLICATION_JSON))) .filter(authenticationMiddleware) - .clientConnector(baseSslHttpClientConnector) + .clientConnector(httpConnector) .build(); } @@ -88,11 +158,11 @@ public HubClient hubClient( @Qualifier("HUB_AUTH_WEB_CLIENT") @Bean WebClient hubAuthWebClient( - @Qualifier("BASE_SSL_HTTP_CLIENT_CONNECTOR") ReactorClientHttpConnector baseSslHttpClientConnector) { + @Qualifier("CORE_HTTP_CONNECTOR") ReactorClientHttpConnector httpConnector) { return WebClient.builder() .baseUrl(hubAuthBaseUrl) .defaultHeaders(httpHeaders -> httpHeaders.setAccept(List.of(MediaType.APPLICATION_JSON))) - .clientConnector(baseSslHttpClientConnector) + .clientConnector(httpConnector) .build(); } diff --git a/src/main/java/de/privateaim/node_message_broker/common/CommonSslSpringConfig.java b/src/main/java/de/privateaim/node_message_broker/common/CommonSslSpringConfig.java index 974fb5f..f1b3ba8 100644 --- a/src/main/java/de/privateaim/node_message_broker/common/CommonSslSpringConfig.java +++ b/src/main/java/de/privateaim/node_message_broker/common/CommonSslSpringConfig.java @@ -7,8 +7,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import reactor.netty.http.client.HttpClient; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; @@ -45,15 +43,6 @@ public class CommonSslSpringConfig { @Value("${app.security.additionalTrustedCertsFile}") private String additionalTrustedCertsFile; - @Qualifier("BASE_SSL_HTTP_CLIENT_CONNECTOR") - @Bean - ReactorClientHttpConnector baseHttpClientConnector(@Qualifier("COMMON_NETTY_SSL_CONTEXT") SslContext sslContext) { - return new ReactorClientHttpConnector( - HttpClient - .create() - .secure(t -> t.sslContext(sslContext))); - } - @Qualifier("COMMON_NETTY_SSL_CONTEXT") @Bean SslContext sslContextNetty(@Qualifier("COMMON_TRUST_MANAGER_FACTORY") TrustManagerFactory tmf) throws IOException { diff --git a/src/main/java/de/privateaim/node_message_broker/message/MessageSpringConfig.java b/src/main/java/de/privateaim/node_message_broker/message/MessageSpringConfig.java index 9e7dc4a..3abd715 100644 --- a/src/main/java/de/privateaim/node_message_broker/message/MessageSpringConfig.java +++ b/src/main/java/de/privateaim/node_message_broker/message/MessageSpringConfig.java @@ -18,6 +18,7 @@ import io.socket.client.Manager; import io.socket.client.Socket; import lombok.extern.slf4j.Slf4j; +import okhttp3.Credentials; import okhttp3.OkHttpClient; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.openssl.PEMKeyPair; @@ -38,8 +39,12 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; +import java.net.InetSocketAddress; +import java.net.Proxy; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.security.SecureRandom; import java.security.interfaces.ECPrivateKey; import java.util.Base64; @@ -47,6 +52,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.Function; +import java.util.regex.Pattern; @Slf4j @Configuration @@ -60,19 +66,80 @@ class MessageSpringConfig { @Value("${app.hub.auth.robotId}") private String selfRobotId; + @Value("${app.proxy.host}") + private String proxyHost; + + @Value("${app.proxy.port}") + private Integer proxyPort; + + @Value("${app.proxy.whitelist}") + private String proxyWhitelist; + + @Value("${app.proxy.username}") + private String proxyUsername; + + @Value("${app.proxy.passwordFile}") + private String proxyPasswordFile; + private static final String SOCKET_RECEIVE_HUB_MESSAGE_IDENTIFIER = "send"; @Qualifier("HUB_MESSENGER_UNDERLYING_SOCKET_SECURE_CLIENT") @Bean - OkHttpClient socketBaseClient(@Qualifier("COMMON_JAVA_SSL_CONTEXT") SSLContext sslCtx, - @Qualifier("COMMON_TRUST_MANAGER_FACTORY") TrustManagerFactory tmf) { - return new OkHttpClient.Builder() - .sslSocketFactory(sslCtx.getSocketFactory(), (X509TrustManager) tmf.getTrustManagers()[0]) - .readTimeout(1, TimeUnit.MINUTES) - .build(); + OkHttpClient decoratedSocketBaseClient(@Qualifier("COMMON_JAVA_SSL_CONTEXT") SSLContext sslCtx, + @Qualifier("COMMON_TRUST_MANAGER_FACTORY") TrustManagerFactory tmf) { + + var clientBuilder = new OkHttpClient.Builder() + .readTimeout(1, TimeUnit.MINUTES); + decorateClientWithSSLContext(clientBuilder, sslCtx, tmf); + decorateClientWithProxySettings(clientBuilder); + + return clientBuilder.build(); + } + + + // Additional SSL configuration that's orthogonal to the one used in the core HTTP client. + // The reason for that is that socket.io decided to go with a specific HTTP client instead of using an interface. + // Hence, we're bound to using that client which also comes with a specific way of configuring it. + private void decorateClientWithSSLContext(OkHttpClient.Builder clientBuilder, SSLContext sslCtx, + TrustManagerFactory tmf) { + clientBuilder.sslSocketFactory(sslCtx.getSocketFactory(), (X509TrustManager) tmf.getTrustManagers()[0]); } + // Additional proxy configuration that's orthogonal to the one used in the core HTTP client. + // The reason for that is that socket.io decided to go with a specific HTTP client instead of using an interface. + // Hence, we're bound to using that client which also comes with a specific way of configuring it. + private void decorateClientWithProxySettings(OkHttpClient.Builder clientBuilder) { + var proxyWhitelistPattern = Pattern.compile(proxyWhitelist); + if (proxyWhitelistPattern.matcher(proxyHost).matches()) { + log.warn("skipping proxy configuration for message socket due to the host `{}` matching the proxy " + + "whitelist with pattern `{}`", proxyHost, proxyWhitelistPattern); + return; + } + + log.info("configuring usage of proxy for message socket at `{}:{}`", proxyHost, proxyPort); + var proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)); + clientBuilder.proxy(proxy); + + if (proxyUsername != null && proxyPasswordFile != null) { + try { + var proxyPassword = Files.readString(Paths.get(proxyPasswordFile)); + + log.info("configuring authentication for proxy of message socket"); + clientBuilder.proxyAuthenticator((route, response) -> { + var proxyCredentials = Credentials.basic(proxyUsername, proxyPassword); + return response.request().newBuilder() + .header("Proxy-Authorization", proxyCredentials) + .build(); + }); + } catch (IOException e) { + log.error("cannot read password file for proxy at `{}`", proxyPasswordFile, e); + throw new RuntimeException(e); + } + } + } + + @Qualifier("HUB_MESSENGER_UNDERLYING_SOCKET") @Bean(destroyMethod = "disconnect") public Socket underlyingMessengerSocket( @@ -235,10 +302,10 @@ public MessageService messageService( @Qualifier("HUB_MESSAGE_RECEIVE_FORWARD_WEB_CLIENT") @Bean public WebClient messageForwardWebClient( - @Qualifier("BASE_SSL_HTTP_CLIENT_CONNECTOR") ReactorClientHttpConnector baseSslHttpClientConnector) { + @Qualifier("CORE_HTTP_CONNECTOR") ReactorClientHttpConnector httpConnector) { return WebClient.builder() .defaultHeaders(httpHeaders -> httpHeaders.setAccept(List.of(MediaType.APPLICATION_JSON))) - .clientConnector(baseSslHttpClientConnector) + .clientConnector(httpConnector) .build(); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index a1236b9..74cce4d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -36,6 +36,12 @@ logging: org.springframework.web: ${LOG_LEVEL:info} app: + proxy: + host: ${PROXY_HOST:} + port: ${PROXY_PORT:} + whitelist: ${PROXY_WHITELIST:} + username: ${PROXY_USERNAME:} + passwordFile: ${PROXY_PASSWORD_FILE:} auth: jwksUrl: ${AUTH_JWKS_URL} hub: From c2f6236a56bae1108f9afb0374ac634e90fac14f Mon Sep 17 00:00:00 2001 From: Alexander Twrdik <6052859+DiCanio@users.noreply.github.com> Date: Wed, 6 Aug 2025 12:53:11 +0200 Subject: [PATCH 2/4] Properly Check for Proxy Related Variables Due to how the application gets configured using its `application.yml` file, proxy related settings will never be null, even if the corresponding env vars are not set. Hence, checks need to take this into account and check for blank strings instead. Without this change authentication would always be configured (just a single example). --- .../common/CommonSpringConfig.java | 18 ++++++++++++++---- .../message/MessageSpringConfig.java | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/de/privateaim/node_message_broker/common/CommonSpringConfig.java b/src/main/java/de/privateaim/node_message_broker/common/CommonSpringConfig.java index f297793..7eaba9e 100644 --- a/src/main/java/de/privateaim/node_message_broker/common/CommonSpringConfig.java +++ b/src/main/java/de/privateaim/node_message_broker/common/CommonSpringConfig.java @@ -95,16 +95,23 @@ private void decorateClientWithSSLContext(HttpClient client, SslContext sslConte } private void decorateClientWithProxySettings(HttpClient client) { - if (proxyHost != null && proxyPort != null) { + if (!proxyHost.isBlank() && proxyPort != null) { log.info("configuring usage of proxy at `{}:{}` with the following hosts whitelisted (via regex): `{}`", proxyHost, proxyPort, proxyWhitelist); client.proxy(proxy -> { var proxyBuilder = proxy.type(ProxyProvider.Proxy.HTTP) .host(proxyHost) - .port(proxyPort) - .nonProxyHosts(proxyWhitelist); + .port(proxyPort); + + if (!proxyWhitelist.isBlank()) { + log.info("configuring whitelist for proxy at `{}:{}`", proxyHost, proxyPort); + proxyBuilder.nonProxyHosts(proxyWhitelist); + } else { + log.info("skipping whitelist configuration for proxy at `{}:{}` since no whitelist " + + "is configured", proxyHost, proxyPort); + } - if (proxyUsername != null && proxyPasswordFile != null) { + if (!proxyUsername.isBlank() && !proxyPasswordFile.isBlank()) { try { log.info("configuring authentication for proxy"); var proxyPassword = Files.readString(Paths.get(proxyPasswordFile)); @@ -114,6 +121,9 @@ private void decorateClientWithProxySettings(HttpClient client) { log.error("cannot read password file for proxy at `{}`", proxyPasswordFile, e); throw new RuntimeException(e); } + } else { + log.info("skipping authentication configuration for proxy at `{}:{}` since no " + + "credentials are configured", proxyHost, proxyPort); } } ); diff --git a/src/main/java/de/privateaim/node_message_broker/message/MessageSpringConfig.java b/src/main/java/de/privateaim/node_message_broker/message/MessageSpringConfig.java index 3abd715..6a62250 100644 --- a/src/main/java/de/privateaim/node_message_broker/message/MessageSpringConfig.java +++ b/src/main/java/de/privateaim/node_message_broker/message/MessageSpringConfig.java @@ -121,7 +121,7 @@ private void decorateClientWithProxySettings(OkHttpClient.Builder clientBuilder) var proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)); clientBuilder.proxy(proxy); - if (proxyUsername != null && proxyPasswordFile != null) { + if (!proxyUsername.isBlank() && !proxyPasswordFile.isBlank()) { try { var proxyPassword = Files.readString(Paths.get(proxyPasswordFile)); From e2c59640e6e34483ef22cf3f87e2b8876915d277 Mon Sep 17 00:00:00 2001 From: Alexander Twrdik <6052859+DiCanio@users.noreply.github.com> Date: Wed, 6 Aug 2025 12:55:41 +0200 Subject: [PATCH 3/4] Pass On HTTP Client References Ensures that configured HTTP client references are returned from their corresponding configuration functions. Otherwise, they would simply configure a reference in-place which is only valid within the scope of the configuration function itself. Without this change, SSL and proxy configurations are not working. --- .../common/CommonSpringConfig.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/de/privateaim/node_message_broker/common/CommonSpringConfig.java b/src/main/java/de/privateaim/node_message_broker/common/CommonSpringConfig.java index 7eaba9e..3684cf3 100644 --- a/src/main/java/de/privateaim/node_message_broker/common/CommonSpringConfig.java +++ b/src/main/java/de/privateaim/node_message_broker/common/CommonSpringConfig.java @@ -84,21 +84,21 @@ HttpRetryConfig exchangeRetryConfig() { @Bean HttpClient decoratedHttpClient(@Qualifier("COMMON_NETTY_SSL_CONTEXT") SslContext sslContext) { var client = HttpClient.create(); - decorateClientWithSSLContext(client, sslContext); - decorateClientWithProxySettings(client); + client = decorateClientWithSSLContext(client, sslContext); + client = decorateClientWithProxySettings(client); return client; } - private void decorateClientWithSSLContext(HttpClient client, SslContext sslContext) { - client.secure(t -> t.sslContext(sslContext)); + private HttpClient decorateClientWithSSLContext(HttpClient client, SslContext sslContext) { + return client.secure(t -> t.sslContext(sslContext)); } - private void decorateClientWithProxySettings(HttpClient client) { + private HttpClient decorateClientWithProxySettings(HttpClient client) { if (!proxyHost.isBlank() && proxyPort != null) { log.info("configuring usage of proxy at `{}:{}` with the following hosts whitelisted (via regex): `{}`", proxyHost, proxyPort, proxyWhitelist); - client.proxy(proxy -> { + return client.proxy(proxy -> { var proxyBuilder = proxy.type(ProxyProvider.Proxy.HTTP) .host(proxyHost) .port(proxyPort); @@ -129,6 +129,7 @@ private void decorateClientWithProxySettings(HttpClient client) { ); } else { log.info("skipping proxy configuration due to no specified settings"); + return client; } } From 2e3e58bddd0929d04ab150db9738bc968092fbdb Mon Sep 17 00:00:00 2001 From: Alexander Twrdik <6052859+DiCanio@users.noreply.github.com> Date: Wed, 6 Aug 2025 12:58:34 +0200 Subject: [PATCH 4/4] Add NGINX Forward Proxy for Dev Purposes Adds a single NGINX instance to the dev setup for development purposes. The instance is configured so that incoming requests are simply forwarded to their corresponding destinations. Additionally, the X-Forwarded-For header is set so that the full IP chain is preserved. --- dev/config/proxy/nginx.conf | 15 +++++++++++++++ dev/docker-compose.yml | 12 ++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 dev/config/proxy/nginx.conf diff --git a/dev/config/proxy/nginx.conf b/dev/config/proxy/nginx.conf new file mode 100644 index 0000000..e66faae --- /dev/null +++ b/dev/config/proxy/nginx.conf @@ -0,0 +1,15 @@ +events { + worker_connections 4096; +} + +http { + server { + listen 8888; + + location / { + resolver 8.8.8.8; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://$http_host$uri$is_args$args; + } + } +} diff --git a/dev/docker-compose.yml b/dev/docker-compose.yml index e6389a1..f737ddd 100644 --- a/dev/docker-compose.yml +++ b/dev/docker-compose.yml @@ -11,8 +11,8 @@ services: keycloak: image: keycloak/keycloak:24.0.4@sha256:ff02c932f0249c58f32b8ff1b188a48cc90809779a3a05931ab67f5672400ad0 ports: - - 18080:8080 - - 10433:8433 + - "18080:8080" + - "10433:8433" command: - start-dev - --health-enabled @@ -23,3 +23,11 @@ services: KEYCLOAK_ADMIN_PASSWORD: admin volumes: - ./config/keycloak/private-aim-realm.json:/opt/keycloak/data/import/default-path-realm.json:ro + proxy: + image: nginx:1.29.0-alpine@sha256:d67ea0d64d518b1bb04acde3b00f722ac3e9764b3209a9b0a98924ba35e4b779 + ports: + - "8888:8888" + volumes: + - ./config/proxy/nginx.conf:/etc/nginx/nginx.conf:ro + command: [nginx-debug, '-g', 'daemon off;'] +