KEYCLOAK-19080 Simplify the RHSSO setup in an OpenShift Disconnected cluster
KEYCLOAK-19080 Simplify the RHSSO setup in an OpenShift Disconnected cluster
This commit is contained in:
parent
7010017e0e
commit
7d0af8519b
3 changed files with 111 additions and 3 deletions
|
@ -39,6 +39,8 @@ import java.util.concurrent.TimeUnit;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
|
|
||||||
|
import static org.keycloak.utils.StringUtil.isBlank;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default {@link HttpClientFactory} for {@link HttpClientProvider HttpClientProvider's} used by Keycloak for outbound HTTP calls.
|
* The default {@link HttpClientFactory} for {@link HttpClientProvider HttpClientProvider's} used by Keycloak for outbound HTTP calls.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -63,6 +65,10 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
|
||||||
private static final Logger logger = Logger.getLogger(DefaultHttpClientFactory.class);
|
private static final Logger logger = Logger.getLogger(DefaultHttpClientFactory.class);
|
||||||
private static final String configScope = "keycloak.connectionsHttpClient.default.";
|
private static final String configScope = "keycloak.connectionsHttpClient.default.";
|
||||||
|
|
||||||
|
private static final String HTTPS_PROXY = "https_proxy";
|
||||||
|
private static final String HTTP_PROXY = "http_proxy";
|
||||||
|
private static final String NO_PROXY = "no_proxy";
|
||||||
|
|
||||||
private volatile CloseableHttpClient httpClient;
|
private volatile CloseableHttpClient httpClient;
|
||||||
private Config.Scope config;
|
private Config.Scope config;
|
||||||
|
|
||||||
|
@ -145,12 +151,27 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
|
||||||
String clientKeystore = config.get("client-keystore");
|
String clientKeystore = config.get("client-keystore");
|
||||||
String clientKeystorePassword = config.get("client-keystore-password");
|
String clientKeystorePassword = config.get("client-keystore-password");
|
||||||
String clientPrivateKeyPassword = config.get("client-key-password");
|
String clientPrivateKeyPassword = config.get("client-key-password");
|
||||||
String[] proxyMappings = config.getArray("proxy-mappings");
|
|
||||||
boolean disableTrustManager = config.getBoolean("disable-trust-manager", false);
|
boolean disableTrustManager = config.getBoolean("disable-trust-manager", false);
|
||||||
|
|
||||||
boolean expectContinueEnabled = getBooleanConfigWithSysPropFallback("expect-continue-enabled", false);
|
boolean expectContinueEnabled = getBooleanConfigWithSysPropFallback("expect-continue-enabled", false);
|
||||||
boolean resuseConnections = getBooleanConfigWithSysPropFallback("reuse-connections", true);
|
boolean resuseConnections = getBooleanConfigWithSysPropFallback("reuse-connections", true);
|
||||||
|
|
||||||
|
// optionally configure proxy mappings
|
||||||
|
// direct SPI config (e.g. via standalone.xml) takes precedence over env vars
|
||||||
|
// lower case env vars take precedence over upper case env vars
|
||||||
|
ProxyMappings proxyMappings = ProxyMappings.valueOf(config.getArray("proxy-mappings"));
|
||||||
|
if (proxyMappings == null || proxyMappings.isEmpty()) {
|
||||||
|
logger.debug("Trying to use proxy mapping from env vars");
|
||||||
|
String httpProxy = getEnvVarValue(HTTPS_PROXY);
|
||||||
|
if (isBlank(httpProxy)) {
|
||||||
|
httpProxy = getEnvVarValue(HTTP_PROXY);
|
||||||
|
}
|
||||||
|
String noProxy = getEnvVarValue(NO_PROXY);
|
||||||
|
|
||||||
|
logger.debugf("httpProxy: %s, noProxy: %s", httpProxy, noProxy);
|
||||||
|
proxyMappings = ProxyMappings.withFixedProxyMapping(httpProxy, noProxy);
|
||||||
|
}
|
||||||
|
|
||||||
HttpClientBuilder builder = new HttpClientBuilder();
|
HttpClientBuilder builder = new HttpClientBuilder();
|
||||||
|
|
||||||
builder.socketTimeout(socketTimeout, TimeUnit.MILLISECONDS)
|
builder.socketTimeout(socketTimeout, TimeUnit.MILLISECONDS)
|
||||||
|
@ -161,7 +182,7 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
|
||||||
.connectionTTL(connectionTTL, TimeUnit.MILLISECONDS)
|
.connectionTTL(connectionTTL, TimeUnit.MILLISECONDS)
|
||||||
.maxConnectionIdleTime(maxConnectionIdleTime, TimeUnit.MILLISECONDS)
|
.maxConnectionIdleTime(maxConnectionIdleTime, TimeUnit.MILLISECONDS)
|
||||||
.disableCookies(disableCookies)
|
.disableCookies(disableCookies)
|
||||||
.proxyMappings(ProxyMappings.valueOf(proxyMappings))
|
.proxyMappings(proxyMappings)
|
||||||
.expectContinueEnabled(expectContinueEnabled)
|
.expectContinueEnabled(expectContinueEnabled)
|
||||||
.reuseConnections(resuseConnections);
|
.reuseConnections(resuseConnections);
|
||||||
|
|
||||||
|
@ -215,4 +236,12 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
|
||||||
return value != null ? value : defaultValue;
|
return value != null ? value : defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getEnvVarValue(String name) {
|
||||||
|
String value = System.getenv(name.toLowerCase());
|
||||||
|
if (isBlank(value)) {
|
||||||
|
value = System.getenv(name.toUpperCase());
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.apache.http.auth.UsernamePasswordCredentials;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -30,6 +31,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.keycloak.utils.StringUtil.isBlank;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ProxyMappings} describes an ordered mapping for hostname regex patterns to a {@link HttpHost} proxy.
|
* {@link ProxyMappings} describes an ordered mapping for hostname regex patterns to a {@link HttpHost} proxy.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -44,9 +47,11 @@ public class ProxyMappings {
|
||||||
|
|
||||||
private static final ProxyMappings EMPTY_MAPPING = valueOf(Collections.emptyList());
|
private static final ProxyMappings EMPTY_MAPPING = valueOf(Collections.emptyList());
|
||||||
|
|
||||||
|
private static final String NO_PROXY_DELIMITER = ",";
|
||||||
|
|
||||||
private final List<ProxyMapping> entries;
|
private final List<ProxyMapping> entries;
|
||||||
|
|
||||||
private static Map<String, ProxyMapping> hostnameToProxyCache = new ConcurrentHashMap<>();
|
private static final Map<String, ProxyMapping> hostnameToProxyCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link ProxyMappings} from the provided {@link ProxyMapping Entries}.
|
* Creates a {@link ProxyMappings} from the provided {@link ProxyMapping Entries}.
|
||||||
|
@ -93,6 +98,34 @@ public class ProxyMappings {
|
||||||
return valueOf(Arrays.asList(proxyMappings));
|
return valueOf(Arrays.asList(proxyMappings));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link ProxyMappings} from provided parameters representing the established {@code HTTP(S)_PROXY}
|
||||||
|
* and {@code NO_PROXY} environment variables.
|
||||||
|
*
|
||||||
|
* @param httpProxy a proxy used for all hosts except the ones specified in {@code noProxy}
|
||||||
|
* @param noProxy a list of hosts (separated by comma) that should not use proxy;
|
||||||
|
* all suffixes are matched too (e.g. redhat.com will also match access.redhat.com)
|
||||||
|
* @return
|
||||||
|
* @see <a href="https://about.gitlab.com/blog/2021/01/27/we-need-to-talk-no-proxy/">https://about.gitlab.com/blog/2021/01/27/we-need-to-talk-no-proxy/</a>
|
||||||
|
*/
|
||||||
|
public static ProxyMappings withFixedProxyMapping(String httpProxy, String noProxy) {
|
||||||
|
List<ProxyMapping> proxyMappings = new ArrayList<>();
|
||||||
|
|
||||||
|
if (!isBlank(httpProxy)) {
|
||||||
|
// noProxy must be first as it's more specific than .*
|
||||||
|
if (!isBlank(noProxy)) {
|
||||||
|
for (String host : noProxy.split(NO_PROXY_DELIMITER)) {
|
||||||
|
// do not support regex in no_proxy
|
||||||
|
proxyMappings.add(new ProxyMapping(Pattern.compile("(?:.+\\.)?" + Pattern.quote(host)), null, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyMappings.add(ProxyMapping.valueOf(".*" + ProxyMapping.DELIMITER + httpProxy));
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxyMappings.isEmpty() ? EMPTY_MAPPING : new ProxyMappings(proxyMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return this.entries.isEmpty();
|
return this.entries.isEmpty();
|
||||||
|
|
|
@ -29,6 +29,8 @@ import java.util.List;
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||||
import static org.hamcrest.CoreMatchers.nullValue;
|
import static org.hamcrest.CoreMatchers.nullValue;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -203,4 +205,48 @@ public class ProxyMappingsTest {
|
||||||
ProxyMapping forSalesForce = proxyMappingsWithProxyAuthen.getProxyFor("login.salesforce.com");
|
ProxyMapping forSalesForce = proxyMappingsWithProxyAuthen.getProxyFor("login.salesforce.com");
|
||||||
assertThat(forSalesForce.getProxyHost().getHostName(), is("fallback"));
|
assertThat(forSalesForce.getProxyHost().getHostName(), is("fallback"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReturnMappingForHttpProxy() {
|
||||||
|
ProxyMappings proxyMappings = ProxyMappings.withFixedProxyMapping("https://some-proxy.redhat.com:8080", null);
|
||||||
|
|
||||||
|
ProxyMapping forGoogle = proxyMappings.getProxyFor("login.google.com");
|
||||||
|
assertEquals("some-proxy.redhat.com", forGoogle.getProxyHost().getHostName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReturnMappingForHttpProxyWithNoProxy() {
|
||||||
|
ProxyMappings proxyMappings = ProxyMappings.withFixedProxyMapping("https://some-proxy.redhat.com:8080", "login.facebook.com");
|
||||||
|
|
||||||
|
assertEquals("some-proxy.redhat.com", proxyMappings.getProxyFor("login.google.com").getProxyHost().getHostName());
|
||||||
|
assertEquals("some-proxy.redhat.com", proxyMappings.getProxyFor("facebook.com").getProxyHost().getHostName());
|
||||||
|
|
||||||
|
assertNull(proxyMappings.getProxyFor("login.facebook.com").getProxyHost());
|
||||||
|
assertNull(proxyMappings.getProxyFor("auth.login.facebook.com").getProxyHost());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReturnMappingForHttpProxyWithMultipleNoProxy() {
|
||||||
|
ProxyMappings proxyMappings = ProxyMappings.withFixedProxyMapping("https://some-proxy.redhat.com:8080", "login.facebook.com,corp.com");
|
||||||
|
|
||||||
|
assertEquals("some-proxy.redhat.com", proxyMappings.getProxyFor("login.google.com").getProxyHost().getHostName());
|
||||||
|
assertEquals("some-proxy.redhat.com", proxyMappings.getProxyFor("facebook.com").getProxyHost().getHostName());
|
||||||
|
|
||||||
|
assertNull(proxyMappings.getProxyFor("login.facebook.com").getProxyHost());
|
||||||
|
assertNull(proxyMappings.getProxyFor("auth.login.facebook.com").getProxyHost());
|
||||||
|
assertNull(proxyMappings.getProxyFor("myapp.acme.corp.com").getProxyHost());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReturnMappingForNoProxyWithInvalidChars() {
|
||||||
|
ProxyMappings proxyMappings = ProxyMappings.withFixedProxyMapping("https://some-proxy.redhat.com:8080", "[lj]ogin.facebook.com");
|
||||||
|
|
||||||
|
assertEquals("some-proxy.redhat.com", proxyMappings.getProxyFor("login.facebook.com").getProxyHost().getHostName());
|
||||||
|
assertEquals("some-proxy.redhat.com", proxyMappings.getProxyFor("jogin.facebook.com").getProxyHost().getHostName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReturnEmptyMappingForEmptyHttpProxy() {
|
||||||
|
assertNull(ProxyMappings.withFixedProxyMapping(null, "facebook.com"));
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue