Skip to content

Commit e20506a

Browse files
authored
Merge pull request #154 from groldan/ows_through_proxy
Cascaded OWS Stores with HTTP proxy externalized configuration
2 parents b54e73e + bdd2beb commit e20506a

File tree

10 files changed

+725
-1
lines changed

10 files changed

+725
-1
lines changed

docs/configuration/index.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Cloud Native GeoServer externalized configuration guide
2+
3+
## Contents
4+
{:.no_toc}
5+
6+
* Will be replaced with the ToC, excluding the "Contents" header
7+
{:toc}
8+
9+
## Introduction
10+
11+
TBD
12+
13+
## GeoServer configuration properties
14+
15+
## HTTP proxy for cascaded OWS (WMS/WMTS/WFS) Stores
16+
17+
Cascaded OWS stores make use of a SPI (Service Provider Interface)
18+
extension point to configure the appropriate GeoTools `HTTPClientFactory`.
19+
20+
We provide a Spring Boot Auto-configuration that can be configured
21+
through regular spring-boot externalized properties and only affects
22+
GeoTools HTTP clients instead of the whole JVM.
23+
24+
The usual way to set an http proxy is through the `http.proxyHost`, `http.proxyPort`,
25+
`http.proxyUser`, `http.proxyPassword` Java System Properties.
26+
27+
In the context of Cloud Native GeoServer containerized applications,
28+
this presents a number of drawbacks:
29+
30+
* Standard Java proxy parameters only work with System properties,
31+
not OS environment variables, and setting system properties is more
32+
cumbersome than env variables (you have to modify the container run command).
33+
* `http.proxyUser/Password` are not standard properties, though commonly used, it's kind of
34+
JDK implementation dependent.
35+
* Setting `-Dhttp.proxy* System properties affects all HTTP clients in the container, meaning
36+
requests to the config-service, discovery-service, etc., will also try to go through the proxy,
37+
or you need to go through the extra burden of figuring out how to ignore them.
38+
* If the proxy is secured, and since the http client used may not respect the
39+
http.proxyUser/Password parameters, the apps won't start since they'll get
40+
HTTP 407 "Proxy Authentication Required".
41+
42+
The following externalized configuration properties apply, with these suggested default values:
43+
44+
```yaml
45+
# GeoTools HTTP Client proxy configuration, allows configuring cascaded WMS/WMTS/WFS stores
46+
# that need to go through an HTTP proxy without affecting all the http clients at the JVM level
47+
# These are default settings. The enabled property can be set to false to disable the custom
48+
# HTTPClientFactory altogether.
49+
# The following OS environment variables can be set for easier configuration:
50+
# HTTP(S)_PROXYHOST, HTTP(S)_PROXYPORT, HTTP(S)_PROXYUSER, HTTP(S)_PROXYPASSWORD, HTTP(S)_NONPROXYHOSTS
51+
geotools:
52+
httpclient:
53+
proxy:
54+
enabled: true
55+
http:
56+
host: ${http.proxyHost:}
57+
port: ${http.proxyPort:}
58+
user: ${http.proxyUser:}
59+
password: ${http.proxyPassword:}
60+
nonProxyHosts: ${http.nonProxyHosts:localhost.*}
61+
# comma separated list of Java regular expressions, e.g.: nonProxyHosts: localhost, example.*
62+
https:
63+
host: ${https.proxyHost:${geotools.httpclient.proxy.http.host}}
64+
port: ${https.proxyPort:${geotools.httpclient.proxy.http.port}}
65+
user: ${https.proxyUser:${geotools.httpclient.proxy.http.user}}
66+
password: ${https.proxyPassword:${geotools.httpclient.proxy.http.password}}
67+
nonProxyHosts: ${https.nonProxyHosts:${geotools.httpclient.proxy.http.nonProxyHosts}}
68+
```
69+
70+
### Configure HTTP proxy with environment variables in docker-compose.yml
71+
72+
As mentioned above, regular JVM proxy configuration works with Java System properties
73+
but not with Operating System environment variables.
74+
75+
The above `geotools.httpclient.proxy` config properties though allow to do so
76+
easily as in the following `docker-compose.yml` snippet:
77+
78+
```yaml
79+
version: "3.8"
80+
...
81+
services:
82+
...
83+
wms:
84+
image: geoservercloud/geoserver-cloud-wms:<version>
85+
environment:
86+
HTTP_PROXYHOST: 192.168.86.26
87+
HTTP_PROXYPORT: 80
88+
HTTP_PROXYUSER: jack
89+
HTTP_PROXYPASSWORD: insecure
90+
...
91+
```

docs/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,7 @@ Things to mention
213213
214214
** Gateway: custom filter to adhere to OWS case-insensitive parameter names
215215
-->
216+
217+
# Configuration guide
218+
219+
Check out the [Externalized configuration guide](configuration/index.md) to learn about fine tuning GeoServer-Cloud applications.

starters/catalog-backend-starter/src/main/java/org/geoserver/cloud/autoconfigure/catalog/GeoServerBackendAutoConfiguration.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package org.geoserver.cloud.autoconfigure.catalog;
66

77
import org.geoserver.cloud.config.catalog.CoreBackendConfiguration;
8+
import org.geotools.autoconfigure.httpclient.GeoToolsHttpClientAutoConfiguration;
89
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
910
import org.springframework.context.annotation.Configuration;
1011
import org.springframework.context.annotation.Import;
@@ -19,6 +20,7 @@
1920
* (provided their configs are included in this class' {@code @Import} list)
2021
*/
2122
@Configuration(proxyBeanMethods = false)
23+
@AutoConfigureAfter(GeoToolsHttpClientAutoConfiguration.class)
2224
@Import({ //
2325
CoreBackendConfiguration.class, //
2426
DataDirectoryAutoConfiguration.class, //
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* (c) 2021 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
3+
* GPL 2.0 license, available at the root application directory.
4+
*/
5+
package org.geotools.autoconfigure.httpclient;
6+
7+
import lombok.extern.slf4j.Slf4j;
8+
import org.geotools.autoconfigure.httpclient.ProxyConfig.ProxyHostConfig;
9+
import org.geotools.http.HTTPClientFactory;
10+
import org.springframework.beans.factory.annotation.Autowired;
11+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
12+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
13+
import org.springframework.boot.context.properties.ConfigurationProperties;
14+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
15+
import org.springframework.context.annotation.Bean;
16+
import org.springframework.context.annotation.Configuration;
17+
18+
/**
19+
* {@link EnableAutoConfiguration @EnableAutoConfiguration} auto configuration for a GeoTools {@link
20+
* HTTPClientFactory} that can be configured through spring-boot externalized properties and only
21+
* affects GeoTools http clients instead of the whole JVM.
22+
*
23+
* <p>The usual way to set an http proxy is through the {@literal http.proxyHost}, {@literal
24+
* http.proxyPort}, {@literal http.proxyUser}, {@literal http.proxyPassword} Java System Properties.
25+
*
26+
* <p>In the context of Cloud Native GeoServer containerized applications, this has a number of
27+
* drawbacks:
28+
*
29+
* <ul>
30+
* <li>Standard java proxy parameters only work with System properties, not env variables (at
31+
* least with the apache http client), and setting system properties is more cumbersome than
32+
* env variables (you have to modify the container run command)
33+
* <li>{@literal http.proxyUser/Password} are not standard properties, though commonly used, it's
34+
* kind of JDK implementation dependent.
35+
* <li>Setting {@literal -Dhtt.proxy*} System properties affects all HTTP clients in the
36+
* container, meaning requests to the {@literal config-service}, {@literal discovery-service},
37+
* etc., will also try to go through the proxy, or you need to go through the extra burden of
38+
* figuring out how to ignore them.
39+
* <li>If the proxy is secured, and since the http client used may not respect the {@literal
40+
* http.proxyUser/Password} parameters, the apps won't start since they'll get HTTP 407 "Proxy
41+
* Authentication Required".
42+
* </ul>
43+
*
44+
* <p>The following externalized configuration properties apply:
45+
*
46+
* <pre>
47+
* <code>
48+
* geotools:
49+
* httpclient:
50+
* proxy:
51+
* # defaults to true, false disables the autoconfiguration and falls back to standard GeoServer behavior
52+
* enabled: true
53+
* http:
54+
* host:
55+
* port:
56+
* user:
57+
* password:
58+
* nonProxyHosts:
59+
* # comma separated list of Java regular expressions, e.g.: nonProxyHosts: localhost, example.*
60+
* https:
61+
* host:
62+
* port:
63+
* user:
64+
* password:
65+
* nonProxyHosts:
66+
* </code>
67+
* </pre>
68+
*/
69+
@Configuration(proxyBeanMethods = false)
70+
@EnableConfigurationProperties
71+
@ConditionalOnProperty(
72+
prefix = "geotools.httpclient.proxy",
73+
name = "enabled",
74+
havingValue = "true",
75+
matchIfMissing = true
76+
)
77+
@Slf4j(topic = "org.geotools.autoconfigure.httpclient")
78+
public class GeoToolsHttpClientAutoConfiguration {
79+
80+
@ConfigurationProperties(prefix = "geotools.httpclient.proxy")
81+
public @Bean ProxyConfig geoToolsHttpProxyConfiguration() {
82+
System.setProperty(
83+
"HTTP_CLIENT_FACTORY",
84+
SpringEnvironmentAwareGeoToolsHttpClientFactory.class.getCanonicalName());
85+
return new ProxyConfig();
86+
}
87+
88+
public @Bean SpringEnvironmentAwareGeoToolsHttpClientFactory
89+
springEnvironmentAwareGeoToolsHttpClientFactory(@Autowired ProxyConfig proxyConfig) {
90+
91+
log.info("Using spring environment aware GeoTools HTTPClientFactory");
92+
log(proxyConfig.getHttp(), "HTTP");
93+
log(proxyConfig.getHttps(), "HTTPS");
94+
SpringEnvironmentAwareGeoToolsHttpClientFactory.setProxyConfig(proxyConfig);
95+
96+
return new SpringEnvironmentAwareGeoToolsHttpClientFactory();
97+
}
98+
99+
private void log(ProxyHostConfig config, String protocol) {
100+
config.host()
101+
.ifPresentOrElse(
102+
host ->
103+
log.info(
104+
"{} proxy configured for GeoTools cascaded OWS stores: {}:{}, secured: {}",
105+
protocol,
106+
host,
107+
config.port(),
108+
config.isSecured()),
109+
() ->
110+
log.info(
111+
"No {} proxy configured for GeoTools cascaded OWS stores",
112+
protocol));
113+
}
114+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* (c) 2021 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
3+
* GPL 2.0 license, available at the root application directory.
4+
*/
5+
package org.geotools.autoconfigure.httpclient;
6+
7+
import java.util.List;
8+
import java.util.Optional;
9+
import java.util.regex.Pattern;
10+
import lombok.Data;
11+
import lombok.NonNull;
12+
import org.springframework.util.StringUtils;
13+
14+
/** */
15+
public @Data class ProxyConfig {
16+
17+
private boolean enabled = true;
18+
private ProxyHostConfig http = new ProxyHostConfig();
19+
private ProxyHostConfig https = new ProxyHostConfig();
20+
21+
public static @Data class ProxyHostConfig {
22+
private String host;
23+
private Integer port;
24+
private String user;
25+
private String password;
26+
private List<Pattern> nonProxyHosts;
27+
28+
public Optional<ProxyHostConfig> forHost(@NonNull String targetHostname) {
29+
if (host().isEmpty()) {
30+
return Optional.empty();
31+
}
32+
for (Pattern p : nonProxyHosts()) {
33+
if (p.matcher(targetHostname).matches()) {
34+
return Optional.empty();
35+
}
36+
}
37+
return Optional.of(this);
38+
}
39+
40+
public List<Pattern> nonProxyHosts() {
41+
return this.nonProxyHosts == null ? List.of() : this.nonProxyHosts;
42+
}
43+
44+
public Optional<String> host() {
45+
return StringUtils.hasLength(this.host) ? Optional.of(this.host) : Optional.empty();
46+
}
47+
48+
public int port() {
49+
return port == null ? 80 : port.intValue();
50+
}
51+
52+
public boolean isSecured() {
53+
return StringUtils.hasLength(host)
54+
&& StringUtils.hasLength(user)
55+
&& StringUtils.hasLength(password);
56+
}
57+
}
58+
59+
public ProxyHostConfig ofProtocol(@NonNull String protocol) {
60+
if ("http".equals(protocol)) return http == null ? new ProxyHostConfig() : http;
61+
if ("https".equals(protocol)) return https == null ? new ProxyHostConfig() : https;
62+
throw new IllegalArgumentException("Uknown protocol " + protocol + ". Expected http(s)");
63+
}
64+
}

0 commit comments

Comments
 (0)