mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-06-28 17:34:17 -04:00
http proxy support in JWT realm (#127337)
http proxy support in JWT realm
This commit is contained in:
parent
0f46959a90
commit
e75e45b933
17 changed files with 339 additions and 54 deletions
6
docs/changelog/127337.yaml
Normal file
6
docs/changelog/127337.yaml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
pr: 127337
|
||||||
|
summary: Http proxy support in JWT realm
|
||||||
|
area: Authentication
|
||||||
|
type: enhancement
|
||||||
|
issues:
|
||||||
|
- 114956
|
|
@ -29,6 +29,7 @@ import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static org.elasticsearch.xpack.core.security.authc.support.SecuritySettingsUtil.verifyNonNullNotEmpty;
|
import static org.elasticsearch.xpack.core.security.authc.support.SecuritySettingsUtil.verifyNonNullNotEmpty;
|
||||||
|
import static org.elasticsearch.xpack.core.security.authc.support.SecuritySettingsUtil.verifyProxySettings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings unique to each JWT realm.
|
* Settings unique to each JWT realm.
|
||||||
|
@ -193,7 +194,10 @@ public class JwtRealmSettings {
|
||||||
HTTP_CONNECTION_READ_TIMEOUT,
|
HTTP_CONNECTION_READ_TIMEOUT,
|
||||||
HTTP_SOCKET_TIMEOUT,
|
HTTP_SOCKET_TIMEOUT,
|
||||||
HTTP_MAX_CONNECTIONS,
|
HTTP_MAX_CONNECTIONS,
|
||||||
HTTP_MAX_ENDPOINT_CONNECTIONS
|
HTTP_MAX_ENDPOINT_CONNECTIONS,
|
||||||
|
HTTP_PROXY_SCHEME,
|
||||||
|
HTTP_PROXY_HOST,
|
||||||
|
HTTP_PROXY_PORT
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
// Standard TLS connection settings for outgoing connections to get JWT issuer jwkset_path
|
// Standard TLS connection settings for outgoing connections to get JWT issuer jwkset_path
|
||||||
|
@ -481,6 +485,49 @@ public class JwtRealmSettings {
|
||||||
key -> Setting.intSetting(key, DEFAULT_HTTP_MAX_ENDPOINT_CONNECTIONS, MIN_HTTP_MAX_ENDPOINT_CONNECTIONS, Setting.Property.NodeScope)
|
key -> Setting.intSetting(key, DEFAULT_HTTP_MAX_ENDPOINT_CONNECTIONS, MIN_HTTP_MAX_ENDPOINT_CONNECTIONS, Setting.Property.NodeScope)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
public static final Setting.AffixSetting<String> HTTP_PROXY_HOST = Setting.affixKeySetting(
|
||||||
|
RealmSettings.realmSettingPrefix(TYPE),
|
||||||
|
"http.proxy.host",
|
||||||
|
key -> Setting.simpleString(key, new Setting.Validator<>() {
|
||||||
|
@Override
|
||||||
|
public void validate(String value) {
|
||||||
|
// There is no point in validating the hostname in itself without the scheme and port
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate(String value, Map<Setting<?>, Object> settings) {
|
||||||
|
verifyProxySettings(key, value, settings, HTTP_PROXY_HOST, HTTP_PROXY_SCHEME, HTTP_PROXY_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Setting<?>> settings() {
|
||||||
|
final String namespace = HTTP_PROXY_HOST.getNamespace(HTTP_PROXY_HOST.getConcreteSetting(key));
|
||||||
|
final List<Setting<?>> settings = List.of(
|
||||||
|
HTTP_PROXY_PORT.getConcreteSettingForNamespace(namespace),
|
||||||
|
HTTP_PROXY_SCHEME.getConcreteSettingForNamespace(namespace)
|
||||||
|
);
|
||||||
|
return settings.iterator();
|
||||||
|
}
|
||||||
|
}, Setting.Property.NodeScope)
|
||||||
|
);
|
||||||
|
public static final Setting.AffixSetting<Integer> HTTP_PROXY_PORT = Setting.affixKeySetting(
|
||||||
|
RealmSettings.realmSettingPrefix(TYPE),
|
||||||
|
"http.proxy.port",
|
||||||
|
key -> Setting.intSetting(key, 80, 1, 65535, Setting.Property.NodeScope),
|
||||||
|
() -> HTTP_PROXY_HOST
|
||||||
|
);
|
||||||
|
public static final Setting.AffixSetting<String> HTTP_PROXY_SCHEME = Setting.affixKeySetting(
|
||||||
|
RealmSettings.realmSettingPrefix(TYPE),
|
||||||
|
"http.proxy.scheme",
|
||||||
|
key -> Setting.simpleString(
|
||||||
|
key,
|
||||||
|
"http",
|
||||||
|
// TODO allow HTTPS once https://github.com/elastic/elasticsearch/issues/100264 is fixed
|
||||||
|
value -> verifyNonNullNotEmpty(key, value, List.of("http")),
|
||||||
|
Setting.Property.NodeScope
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
// SSL Configuration settings
|
// SSL Configuration settings
|
||||||
|
|
||||||
public static final Collection<Setting.AffixSetting<?>> SSL_CONFIGURATION_SETTINGS = SSLConfigurationSettings.getRealmSettings(TYPE);
|
public static final Collection<Setting.AffixSetting<?>> SSL_CONFIGURATION_SETTINGS = SSLConfigurationSettings.getRealmSettings(TYPE);
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.core.security.authc.oidc;
|
package org.elasticsearch.xpack.core.security.authc.oidc;
|
||||||
|
|
||||||
import org.apache.http.HttpHost;
|
|
||||||
import org.elasticsearch.common.settings.SecureString;
|
import org.elasticsearch.common.settings.SecureString;
|
||||||
import org.elasticsearch.common.settings.Setting;
|
import org.elasticsearch.common.settings.Setting;
|
||||||
import org.elasticsearch.common.util.set.Sets;
|
import org.elasticsearch.common.util.set.Sets;
|
||||||
|
@ -14,6 +13,7 @@ import org.elasticsearch.core.TimeValue;
|
||||||
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
|
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
|
||||||
import org.elasticsearch.xpack.core.security.authc.support.ClaimSetting;
|
import org.elasticsearch.xpack.core.security.authc.support.ClaimSetting;
|
||||||
import org.elasticsearch.xpack.core.security.authc.support.DelegatedAuthorizationSettings;
|
import org.elasticsearch.xpack.core.security.authc.support.DelegatedAuthorizationSettings;
|
||||||
|
import org.elasticsearch.xpack.core.security.authc.support.SecuritySettingsUtil;
|
||||||
import org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings;
|
import org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
@ -234,32 +234,7 @@ public class OpenIdConnectRealmSettings {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validate(String value, Map<Setting<?>, Object> settings) {
|
public void validate(String value, Map<Setting<?>, Object> settings) {
|
||||||
final String namespace = HTTP_PROXY_HOST.getNamespace(HTTP_PROXY_HOST.getConcreteSetting(key));
|
SecuritySettingsUtil.verifyProxySettings(key, value, settings, HTTP_PROXY_HOST, HTTP_PROXY_SCHEME, HTTP_PROXY_PORT);
|
||||||
final Setting<Integer> portSetting = HTTP_PROXY_PORT.getConcreteSettingForNamespace(namespace);
|
|
||||||
final Integer port = (Integer) settings.get(portSetting);
|
|
||||||
final Setting<String> schemeSetting = HTTP_PROXY_SCHEME.getConcreteSettingForNamespace(namespace);
|
|
||||||
final String scheme = (String) settings.get(schemeSetting);
|
|
||||||
try {
|
|
||||||
new HttpHost(value, port, scheme);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"HTTP host for hostname ["
|
|
||||||
+ value
|
|
||||||
+ "] (from ["
|
|
||||||
+ key
|
|
||||||
+ "]),"
|
|
||||||
+ " port ["
|
|
||||||
+ port
|
|
||||||
+ "] (from ["
|
|
||||||
+ portSetting.getKey()
|
|
||||||
+ "]) and "
|
|
||||||
+ "scheme ["
|
|
||||||
+ scheme
|
|
||||||
+ "] (from (["
|
|
||||||
+ schemeSetting.getKey()
|
|
||||||
+ "]) is invalid"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -7,8 +7,12 @@
|
||||||
|
|
||||||
package org.elasticsearch.xpack.core.security.authc.support;
|
package org.elasticsearch.xpack.core.security.authc.support;
|
||||||
|
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
import org.elasticsearch.common.settings.Setting;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for validating security settings.
|
* Utilities for validating security settings.
|
||||||
|
@ -85,6 +89,45 @@ public final class SecuritySettingsUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void verifyProxySettings(
|
||||||
|
String key,
|
||||||
|
String hostValue,
|
||||||
|
Map<Setting<?>, Object> settings,
|
||||||
|
Setting.AffixSetting<String> hostKey,
|
||||||
|
Setting.AffixSetting<String> schemeKey,
|
||||||
|
Setting.AffixSetting<Integer> portKey
|
||||||
|
) {
|
||||||
|
final String namespace = hostKey.getNamespace(hostKey.getConcreteSetting(key));
|
||||||
|
|
||||||
|
final Setting<Integer> portSetting = portKey.getConcreteSettingForNamespace(namespace);
|
||||||
|
final Integer port = (Integer) settings.get(portSetting);
|
||||||
|
|
||||||
|
final Setting<String> schemeSetting = schemeKey.getConcreteSettingForNamespace(namespace);
|
||||||
|
final String scheme = (String) settings.get(schemeSetting);
|
||||||
|
|
||||||
|
try {
|
||||||
|
new HttpHost(hostValue, port, scheme);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"HTTP host for hostname ["
|
||||||
|
+ hostValue
|
||||||
|
+ "] (from ["
|
||||||
|
+ key
|
||||||
|
+ "]),"
|
||||||
|
+ " port ["
|
||||||
|
+ port
|
||||||
|
+ "] (from ["
|
||||||
|
+ portSetting.getKey()
|
||||||
|
+ "]) and "
|
||||||
|
+ "scheme ["
|
||||||
|
+ scheme
|
||||||
|
+ "] (from (["
|
||||||
|
+ schemeSetting.getKey()
|
||||||
|
+ "]) is invalid"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private SecuritySettingsUtil() {
|
private SecuritySettingsUtil() {
|
||||||
throw new IllegalAccessError("not allowed!");
|
throw new IllegalAccessError("not allowed!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import com.nimbusds.jwt.JWT;
|
||||||
import com.nimbusds.jwt.SignedJWT;
|
import com.nimbusds.jwt.SignedJWT;
|
||||||
|
|
||||||
import org.apache.http.HttpEntity;
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.StatusLine;
|
import org.apache.http.StatusLine;
|
||||||
import org.apache.http.client.config.RequestConfig;
|
import org.apache.http.client.config.RequestConfig;
|
||||||
|
@ -27,6 +28,7 @@ import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
|
||||||
import org.apache.http.impl.nio.client.HttpAsyncClients;
|
import org.apache.http.impl.nio.client.HttpAsyncClients;
|
||||||
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
|
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
|
||||||
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
|
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
|
||||||
|
import org.apache.http.nio.conn.NoopIOSessionStrategy;
|
||||||
import org.apache.http.nio.conn.SchemeIOSessionStrategy;
|
import org.apache.http.nio.conn.SchemeIOSessionStrategy;
|
||||||
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
|
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
|
||||||
import org.apache.http.nio.reactor.ConnectingIOReactor;
|
import org.apache.http.nio.reactor.ConnectingIOReactor;
|
||||||
|
@ -74,6 +76,10 @@ import java.util.function.Supplier;
|
||||||
import javax.net.ssl.HostnameVerifier;
|
import javax.net.ssl.HostnameVerifier;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
|
import static org.elasticsearch.xpack.core.security.authc.jwt.JwtRealmSettings.HTTP_PROXY_HOST;
|
||||||
|
import static org.elasticsearch.xpack.core.security.authc.jwt.JwtRealmSettings.HTTP_PROXY_PORT;
|
||||||
|
import static org.elasticsearch.xpack.core.security.authc.jwt.JwtRealmSettings.HTTP_PROXY_SCHEME;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for JWT realm.
|
* Utilities for JWT realm.
|
||||||
*/
|
*/
|
||||||
|
@ -271,6 +277,7 @@ public class JwtUtil {
|
||||||
final SSLContext clientContext = sslService.sslContext(sslConfiguration);
|
final SSLContext clientContext = sslService.sslContext(sslConfiguration);
|
||||||
final HostnameVerifier verifier = SSLService.getHostnameVerifier(sslConfiguration);
|
final HostnameVerifier verifier = SSLService.getHostnameVerifier(sslConfiguration);
|
||||||
final Registry<SchemeIOSessionStrategy> registry = RegistryBuilder.<SchemeIOSessionStrategy>create()
|
final Registry<SchemeIOSessionStrategy> registry = RegistryBuilder.<SchemeIOSessionStrategy>create()
|
||||||
|
.register("http", NoopIOSessionStrategy.INSTANCE)
|
||||||
.register("https", new SSLIOSessionStrategy(clientContext, verifier))
|
.register("https", new SSLIOSessionStrategy(clientContext, verifier))
|
||||||
.build();
|
.build();
|
||||||
final PoolingNHttpClientConnectionManager connectionManager = new PoolingNHttpClientConnectionManager(ioReactor, registry);
|
final PoolingNHttpClientConnectionManager connectionManager = new PoolingNHttpClientConnectionManager(ioReactor, registry);
|
||||||
|
@ -286,6 +293,15 @@ public class JwtUtil {
|
||||||
final HttpAsyncClientBuilder httpAsyncClientBuilder = HttpAsyncClients.custom()
|
final HttpAsyncClientBuilder httpAsyncClientBuilder = HttpAsyncClients.custom()
|
||||||
.setConnectionManager(connectionManager)
|
.setConnectionManager(connectionManager)
|
||||||
.setDefaultRequestConfig(requestConfig);
|
.setDefaultRequestConfig(requestConfig);
|
||||||
|
if (realmConfig.hasSetting(HTTP_PROXY_HOST)) {
|
||||||
|
httpAsyncClientBuilder.setProxy(
|
||||||
|
new HttpHost(
|
||||||
|
realmConfig.getSetting(HTTP_PROXY_HOST),
|
||||||
|
realmConfig.getSetting(HTTP_PROXY_PORT),
|
||||||
|
realmConfig.getSetting(HTTP_PROXY_SCHEME)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
final CloseableHttpAsyncClient httpAsyncClient = httpAsyncClientBuilder.build();
|
final CloseableHttpAsyncClient httpAsyncClient = httpAsyncClientBuilder.build();
|
||||||
httpAsyncClient.start();
|
httpAsyncClient.start();
|
||||||
return httpAsyncClient;
|
return httpAsyncClient;
|
||||||
|
|
|
@ -23,11 +23,14 @@ import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import static org.elasticsearch.common.Strings.capitalize;
|
import static org.elasticsearch.common.Strings.capitalize;
|
||||||
|
import static org.hamcrest.Matchers.allOf;
|
||||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.emptyIterable;
|
import static org.hamcrest.Matchers.emptyIterable;
|
||||||
|
import static org.hamcrest.Matchers.endsWith;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWT realm settings unit tests. These are low-level tests against ES settings parsers.
|
* JWT realm settings unit tests. These are low-level tests against ES settings parsers.
|
||||||
|
@ -588,4 +591,59 @@ public class JwtRealmSettingsTests extends JwtTestCase {
|
||||||
|
|
||||||
assertThat(e.getMessage(), containsString("required claim [" + fullSettingKey + "] cannot be empty"));
|
assertThat(e.getMessage(), containsString("required claim [" + fullSettingKey + "] cannot be empty"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testInvalidProxySchemeThrowsError() {
|
||||||
|
final String scheme = randomBoolean() ? "https" : randomAlphaOfLengthBetween(3, 8);
|
||||||
|
final String realmName = randomAlphaOfLengthBetween(3, 8);
|
||||||
|
final String proxySchemeSettingKey = RealmSettings.getFullSettingKey(realmName, JwtRealmSettings.HTTP_PROXY_SCHEME);
|
||||||
|
final Settings settings = Settings.builder().put(proxySchemeSettingKey, scheme).build();
|
||||||
|
|
||||||
|
final RealmConfig realmConfig = buildRealmConfig(JwtRealmSettings.TYPE, realmName, settings, randomInt());
|
||||||
|
final IllegalArgumentException e = expectThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> realmConfig.getSetting(JwtRealmSettings.HTTP_PROXY_SCHEME)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
e.getMessage(),
|
||||||
|
equalTo(Strings.format("Invalid value [%s] for [%s]. Allowed values are [http].", scheme, proxySchemeSettingKey))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testInvalidProxyHostThrowsError() {
|
||||||
|
final int proxyPort = randomIntBetween(1, 65535);
|
||||||
|
final String realmName = randomAlphaOfLengthBetween(3, 8);
|
||||||
|
final String proxyPortSettingKey = RealmSettings.getFullSettingKey(realmName, JwtRealmSettings.HTTP_PROXY_PORT);
|
||||||
|
final String proxyHostSettingKey = RealmSettings.getFullSettingKey(realmName, JwtRealmSettings.HTTP_PROXY_HOST);
|
||||||
|
final Settings settings = Settings.builder().put(proxyHostSettingKey, "not a url").put(proxyPortSettingKey, proxyPort).build();
|
||||||
|
|
||||||
|
final RealmConfig realmConfig = buildRealmConfig(JwtRealmSettings.TYPE, realmName, settings, randomInt());
|
||||||
|
final IllegalArgumentException e = expectThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> realmConfig.getSetting(JwtRealmSettings.HTTP_PROXY_HOST)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
e.getMessage(),
|
||||||
|
allOf(startsWith(Strings.format("HTTP host for hostname [not a url] (from [%s])", proxyHostSettingKey)), endsWith("is invalid"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testInvalidProxyPortThrowsError() {
|
||||||
|
final int proxyPort = randomFrom(randomIntBetween(Integer.MIN_VALUE, -1), randomIntBetween(65536, Integer.MAX_VALUE));
|
||||||
|
final String realmName = randomAlphaOfLengthBetween(3, 8);
|
||||||
|
final String proxyPortSettingKey = RealmSettings.getFullSettingKey(realmName, JwtRealmSettings.HTTP_PROXY_PORT);
|
||||||
|
final Settings settings = Settings.builder().put(proxyPortSettingKey, proxyPort).build();
|
||||||
|
|
||||||
|
final RealmConfig realmConfig = buildRealmConfig(JwtRealmSettings.TYPE, realmName, settings, randomInt());
|
||||||
|
final IllegalArgumentException e = expectThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> realmConfig.getSetting(JwtRealmSettings.HTTP_PROXY_PORT)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
e.getMessage(),
|
||||||
|
startsWith(Strings.format("Failed to parse value [%d] for setting [%s]", proxyPort, proxyPortSettingKey))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,12 +98,14 @@ public abstract class JwtTestCase extends ESTestCase {
|
||||||
final boolean includePublicKey = includeRsa || includeEc;
|
final boolean includePublicKey = includeRsa || includeEc;
|
||||||
final boolean includeHmac = randomBoolean() || (includePublicKey == false); // one of HMAC/RSA/EC must be true
|
final boolean includeHmac = randomBoolean() || (includePublicKey == false); // one of HMAC/RSA/EC must be true
|
||||||
final boolean populateUserMetadata = randomBoolean();
|
final boolean populateUserMetadata = randomBoolean();
|
||||||
|
final boolean useJwksEndpoint = randomBoolean();
|
||||||
|
final boolean useProxy = useJwksEndpoint && randomBoolean();
|
||||||
final Path jwtSetPathObj = PathUtils.get(pathHome);
|
final Path jwtSetPathObj = PathUtils.get(pathHome);
|
||||||
final String jwkSetPath = randomBoolean()
|
final String jwkSetPath = useJwksEndpoint
|
||||||
? "https://op.example.com/jwkset.json"
|
? "https://op.example.com/jwkset.json"
|
||||||
: Files.createTempFile(jwtSetPathObj, "jwkset.", ".json").toString();
|
: Files.createTempFile(jwtSetPathObj, "jwkset.", ".json").toString();
|
||||||
|
|
||||||
if (jwkSetPath.equals("https://op.example.com/jwkset.json") == false) {
|
if (useJwksEndpoint == false) {
|
||||||
Files.writeString(PathUtils.get(jwkSetPath), "Non-empty JWK Set Path contents");
|
Files.writeString(PathUtils.get(jwkSetPath), "Non-empty JWK Set Path contents");
|
||||||
}
|
}
|
||||||
final ClientAuthenticationType clientAuthenticationType = randomFrom(ClientAuthenticationType.values());
|
final ClientAuthenticationType clientAuthenticationType = randomFrom(ClientAuthenticationType.values());
|
||||||
|
@ -195,6 +197,16 @@ public abstract class JwtTestCase extends ESTestCase {
|
||||||
.put(RealmSettings.getFullSettingKey(name, SSLConfigurationSettings.TRUSTSTORE_ALGORITHM.realm(JwtRealmSettings.TYPE)), "PKIX")
|
.put(RealmSettings.getFullSettingKey(name, SSLConfigurationSettings.TRUSTSTORE_ALGORITHM.realm(JwtRealmSettings.TYPE)), "PKIX")
|
||||||
.put(RealmSettings.getFullSettingKey(name, SSLConfigurationSettings.CERT_AUTH_PATH.realm(JwtRealmSettings.TYPE)), "ca2.pem");
|
.put(RealmSettings.getFullSettingKey(name, SSLConfigurationSettings.CERT_AUTH_PATH.realm(JwtRealmSettings.TYPE)), "ca2.pem");
|
||||||
|
|
||||||
|
if (useProxy) {
|
||||||
|
if (randomBoolean()) {
|
||||||
|
// Scheme is optional, and defaults to HTTP
|
||||||
|
settingsBuilder.put(RealmSettings.getFullSettingKey(name, JwtRealmSettings.HTTP_PROXY_SCHEME), "http");
|
||||||
|
}
|
||||||
|
|
||||||
|
settingsBuilder.put(RealmSettings.getFullSettingKey(name, JwtRealmSettings.HTTP_PROXY_HOST), "localhost/proxy")
|
||||||
|
.put(RealmSettings.getFullSettingKey(name, JwtRealmSettings.HTTP_PROXY_PORT), randomIntBetween(1, 65535));
|
||||||
|
}
|
||||||
|
|
||||||
final MockSecureSettings secureSettings = new MockSecureSettings();
|
final MockSecureSettings secureSettings = new MockSecureSettings();
|
||||||
if (includeHmac) {
|
if (includeHmac) {
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
|
|
|
@ -47,13 +47,17 @@ import static org.hamcrest.Matchers.hasKey;
|
||||||
*/
|
*/
|
||||||
public class JwtWithOidcAuthIT extends C2IdOpTestCase {
|
public class JwtWithOidcAuthIT extends C2IdOpTestCase {
|
||||||
|
|
||||||
// configured in the Elasticearch node test fixture
|
// configured in the Elasticsearch node test fixture
|
||||||
private static final List<String> ALLOWED_AUDIENCES = List.of("elasticsearch-jwt1", "elasticsearch-jwt2");
|
private static final List<String> ALLOWED_AUDIENCES = List.of("elasticsearch-jwt1", "elasticsearch-jwt2");
|
||||||
private static final String JWT_REALM_NAME = "op-jwt";
|
private static final String JWT_FILE_REALM_NAME = "op-jwt";
|
||||||
|
private static final String JWT_PROXY_REALM_NAME = "op-jwt-proxy";
|
||||||
|
|
||||||
// Constants for role mapping
|
// Constants for role mapping
|
||||||
private static final String ROLE_NAME = "jwt_role";
|
private static final String FILE_ROLE_NAME = "jwt_role";
|
||||||
private static final String SHARED_SECRET = "jwt-realm-shared-secret";
|
private static final String FILE_SHARED_SECRET = "jwt-realm-shared-secret";
|
||||||
|
|
||||||
|
private static final String PROXY_ROLE_NAME = "jwt_proxy_role";
|
||||||
|
private static final String PROXY_SHARED_SECRET = "jwt-proxy-realm-shared-secret";
|
||||||
|
|
||||||
// Randomised values
|
// Randomised values
|
||||||
private static String clientId;
|
private static String clientId;
|
||||||
|
@ -79,10 +83,10 @@ public class JwtWithOidcAuthIT extends C2IdOpTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setupRoleMapping() throws Exception {
|
public void setupRoleMappings() throws Exception {
|
||||||
try (var restClient = getElasticsearchClient()) {
|
try (var restClient = getElasticsearchClient()) {
|
||||||
var client = new TestSecurityClient(restClient);
|
var client = new TestSecurityClient(restClient);
|
||||||
final String mappingJson = Strings.format("""
|
String mappingJson = Strings.format("""
|
||||||
{
|
{
|
||||||
"roles": [ "%s" ],
|
"roles": [ "%s" ],
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
@ -93,8 +97,22 @@ public class JwtWithOidcAuthIT extends C2IdOpTestCase {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
""", ROLE_NAME, JWT_REALM_NAME, TEST_SUBJECT_ID);
|
""", FILE_ROLE_NAME, JWT_FILE_REALM_NAME, TEST_SUBJECT_ID);
|
||||||
client.putRoleMapping(getTestName(), mappingJson);
|
client.putRoleMapping(FILE_ROLE_NAME, mappingJson);
|
||||||
|
|
||||||
|
mappingJson = Strings.format("""
|
||||||
|
{
|
||||||
|
"roles": [ "%s" ],
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"all": [
|
||||||
|
{ "field": { "realm.name": "%s" } },
|
||||||
|
{ "field": { "metadata.jwt_claim_sub": "%s" } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""", PROXY_ROLE_NAME, JWT_PROXY_REALM_NAME, TEST_SUBJECT_ID);
|
||||||
|
client.putRoleMapping(PROXY_ROLE_NAME, mappingJson);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,15 +145,21 @@ public class JwtWithOidcAuthIT extends C2IdOpTestCase {
|
||||||
assertThat("Hash value of URI [" + implicitFlowURI + "] should be a JWT with an id Token", hashParams, hasKey("id_token"));
|
assertThat("Hash value of URI [" + implicitFlowURI + "] should be a JWT with an id Token", hashParams, hasKey("id_token"));
|
||||||
String idJwt = hashParams.get("id_token");
|
String idJwt = hashParams.get("id_token");
|
||||||
|
|
||||||
final Map<String, Object> authenticateResponse = authenticateWithJwtAndSharedSecret(idJwt, SHARED_SECRET);
|
final Map<String, Object> authenticateResponse = authenticateWithJwtAndSharedSecret(idJwt, FILE_SHARED_SECRET);
|
||||||
assertThat(authenticateResponse, Matchers.hasEntry(User.Fields.USERNAME.getPreferredName(), TEST_SUBJECT_ID));
|
assertThat(authenticateResponse, Matchers.hasEntry(User.Fields.USERNAME.getPreferredName(), TEST_SUBJECT_ID));
|
||||||
assertThat(authenticateResponse, Matchers.hasKey(User.Fields.ROLES.getPreferredName()));
|
assertThat(authenticateResponse, Matchers.hasKey(User.Fields.ROLES.getPreferredName()));
|
||||||
assertThat((List<?>) authenticateResponse.get(User.Fields.ROLES.getPreferredName()), contains(ROLE_NAME));
|
assertThat((List<?>) authenticateResponse.get(User.Fields.ROLES.getPreferredName()), contains(FILE_ROLE_NAME));
|
||||||
|
|
||||||
|
// test that the proxy realm successfully loads the JWKS
|
||||||
|
final Map<String, Object> proxyAuthenticateResponse = authenticateWithJwtAndSharedSecret(idJwt, PROXY_SHARED_SECRET);
|
||||||
|
assertThat(proxyAuthenticateResponse, Matchers.hasEntry(User.Fields.USERNAME.getPreferredName(), TEST_SUBJECT_ID));
|
||||||
|
assertThat(proxyAuthenticateResponse, Matchers.hasKey(User.Fields.ROLES.getPreferredName()));
|
||||||
|
assertThat((List<?>) proxyAuthenticateResponse.get(User.Fields.ROLES.getPreferredName()), contains(PROXY_ROLE_NAME));
|
||||||
|
|
||||||
// Use an incorrect shared secret and check it fails
|
// Use an incorrect shared secret and check it fails
|
||||||
ResponseException ex = expectThrows(
|
ResponseException ex = expectThrows(
|
||||||
ResponseException.class,
|
ResponseException.class,
|
||||||
() -> authenticateWithJwtAndSharedSecret(idJwt, "not-" + SHARED_SECRET)
|
() -> authenticateWithJwtAndSharedSecret(idJwt, "not-" + FILE_SHARED_SECRET)
|
||||||
);
|
);
|
||||||
assertThat(ex.getResponse(), TestMatchers.hasStatusCode(RestStatus.UNAUTHORIZED));
|
assertThat(ex.getResponse(), TestMatchers.hasStatusCode(RestStatus.UNAUTHORIZED));
|
||||||
|
|
||||||
|
@ -144,7 +168,7 @@ public class JwtWithOidcAuthIT extends C2IdOpTestCase {
|
||||||
assertThat(dot, greaterThan(0));
|
assertThat(dot, greaterThan(0));
|
||||||
// change the first character of the payload section of the encoded JWT
|
// change the first character of the payload section of the encoded JWT
|
||||||
final String corruptToken = idJwt.substring(0, dot) + "." + transformChar(idJwt.charAt(dot + 1)) + idJwt.substring(dot + 2);
|
final String corruptToken = idJwt.substring(0, dot) + "." + transformChar(idJwt.charAt(dot + 1)) + idJwt.substring(dot + 2);
|
||||||
ex = expectThrows(ResponseException.class, () -> authenticateWithJwtAndSharedSecret(corruptToken, SHARED_SECRET));
|
ex = expectThrows(ResponseException.class, () -> authenticateWithJwtAndSharedSecret(corruptToken, FILE_SHARED_SECRET));
|
||||||
assertThat(ex.getResponse(), TestMatchers.hasStatusCode(RestStatus.UNAUTHORIZED));
|
assertThat(ex.getResponse(), TestMatchers.hasStatusCode(RestStatus.UNAUTHORIZED));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,7 @@ public abstract class C2IdOpTestCase extends ESRestTestCase {
|
||||||
|
|
||||||
private static final String CLIENT_SECRET = "b07efb7a1cf6ec9462afe7b6d3ab55c6c7880262aa61ac28dded292aca47c9a2";
|
private static final String CLIENT_SECRET = "b07efb7a1cf6ec9462afe7b6d3ab55c6c7880262aa61ac28dded292aca47c9a2";
|
||||||
|
|
||||||
private static Network network = Network.newNetwork();
|
private static final Network network = Network.newNetwork();
|
||||||
protected static OidcProviderTestContainer c2id = new OidcProviderTestContainer(network);
|
protected static OidcProviderTestContainer c2id = new OidcProviderTestContainer(network);
|
||||||
protected static HttpProxyTestContainer proxy = new HttpProxyTestContainer(network);
|
protected static HttpProxyTestContainer proxy = new HttpProxyTestContainer(network);
|
||||||
|
|
||||||
|
@ -165,6 +165,17 @@ public abstract class C2IdOpTestCase extends ESRestTestCase {
|
||||||
.setting("xpack.security.authc.realms.jwt.op-jwt.claims.principal", "sub")
|
.setting("xpack.security.authc.realms.jwt.op-jwt.claims.principal", "sub")
|
||||||
.setting("xpack.security.authc.realms.jwt.op-jwt.claims.groups", "groups")
|
.setting("xpack.security.authc.realms.jwt.op-jwt.claims.groups", "groups")
|
||||||
.setting("xpack.security.authc.realms.jwt.op-jwt.client_authentication.type", "shared_secret")
|
.setting("xpack.security.authc.realms.jwt.op-jwt.client_authentication.type", "shared_secret")
|
||||||
|
.setting("xpack.security.authc.realms.jwt.op-jwt-proxy.order", "8")
|
||||||
|
.setting("xpack.security.authc.realms.jwt.op-jwt-proxy.allowed_issuer", () -> c2id.getC2IssuerUrl())
|
||||||
|
.setting("xpack.security.authc.realms.jwt.op-jwt-proxy.allowed_audiences", "elasticsearch-jwt1,elasticsearch-jwt2")
|
||||||
|
.setting("xpack.security.authc.realms.jwt.op-jwt-proxy.pkc_jwkset_path", () -> c2id.getC2IDSslUrl() + "/jwks.json")
|
||||||
|
.setting("xpack.security.authc.realms.jwt.op-jwt-proxy.claims.principal", "sub")
|
||||||
|
.setting("xpack.security.authc.realms.jwt.op-jwt-proxy.claims.groups", "groups")
|
||||||
|
.setting("xpack.security.authc.realms.jwt.op-jwt-proxy.client_authentication.type", "shared_secret")
|
||||||
|
.setting("xpack.security.authc.realms.jwt.op-jwt-proxy.http.proxy.scheme", "http")
|
||||||
|
.setting("xpack.security.authc.realms.jwt.op-jwt-proxy.http.proxy.host", "localhost")
|
||||||
|
.setting("xpack.security.authc.realms.jwt.op-jwt-proxy.http.proxy.port", () -> proxy.getTlsPort().toString())
|
||||||
|
.setting("xpack.security.authc.realms.jwt.op-jwt-proxy.ssl.keystore.path", "testnode.jks")
|
||||||
.keystore("bootstrap.password", "x-pack-test-password")
|
.keystore("bootstrap.password", "x-pack-test-password")
|
||||||
.keystore("xpack.security.http.ssl.keystore.secure_password", "testnode")
|
.keystore("xpack.security.http.ssl.keystore.secure_password", "testnode")
|
||||||
.keystore("xpack.security.authc.realms.oidc.c2id.rp.client_secret", CLIENT_SECRET)
|
.keystore("xpack.security.authc.realms.oidc.c2id.rp.client_secret", CLIENT_SECRET)
|
||||||
|
@ -173,6 +184,8 @@ public abstract class C2IdOpTestCase extends ESRestTestCase {
|
||||||
.keystore("xpack.security.authc.realms.oidc.c2id-post.rp.client_secret", CLIENT_SECRET)
|
.keystore("xpack.security.authc.realms.oidc.c2id-post.rp.client_secret", CLIENT_SECRET)
|
||||||
.keystore("xpack.security.authc.realms.oidc.c2id-jwt.rp.client_secret", CLIENT_SECRET)
|
.keystore("xpack.security.authc.realms.oidc.c2id-jwt.rp.client_secret", CLIENT_SECRET)
|
||||||
.keystore("xpack.security.authc.realms.jwt.op-jwt.client_authentication.shared_secret", "jwt-realm-shared-secret")
|
.keystore("xpack.security.authc.realms.jwt.op-jwt.client_authentication.shared_secret", "jwt-realm-shared-secret")
|
||||||
|
.keystore("xpack.security.authc.realms.jwt.op-jwt-proxy.client_authentication.shared_secret", "jwt-proxy-realm-shared-secret")
|
||||||
|
.keystore("xpack.security.authc.realms.jwt.op-jwt-proxy.ssl.keystore.secure_password", "testnode")
|
||||||
.configFile("testnode.jks", Resource.fromClasspath("ssl/testnode.jks"))
|
.configFile("testnode.jks", Resource.fromClasspath("ssl/testnode.jks"))
|
||||||
.configFile("op-jwks.json", Resource.fromClasspath("op-jwks.json"))
|
.configFile("op-jwks.json", Resource.fromClasspath("op-jwks.json"))
|
||||||
.user("x_pack_rest_user", "x-pack-test-password", "superuser", false)
|
.user("x_pack_rest_user", "x-pack-test-password", "superuser", false)
|
||||||
|
|
|
@ -19,4 +19,6 @@ dependencies {
|
||||||
testImplementation project(':test:framework')
|
testImplementation project(':test:framework')
|
||||||
api project(':test:fixtures:testcontainer-utils')
|
api project(':test:fixtures:testcontainer-utils')
|
||||||
api "junit:junit:${versions.junit}"
|
api "junit:junit:${versions.junit}"
|
||||||
|
|
||||||
|
runtimeOnly "net.java.dev.jna:jna:${versions.jna}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ import org.testcontainers.images.builder.ImageFromDockerfile;
|
||||||
|
|
||||||
public final class HttpProxyTestContainer extends DockerEnvironmentAwareTestContainer {
|
public final class HttpProxyTestContainer extends DockerEnvironmentAwareTestContainer {
|
||||||
|
|
||||||
public static final String DOCKER_BASE_IMAGE = "nginx:latest";
|
|
||||||
private static final Integer PORT = 8888;
|
private static final Integer PORT = 8888;
|
||||||
|
private static final Integer TLS_PORT = 8889;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* for packer caching only
|
* for packer caching only
|
||||||
|
@ -25,15 +25,18 @@ public final class HttpProxyTestContainer extends DockerEnvironmentAwareTestCont
|
||||||
|
|
||||||
public HttpProxyTestContainer(Network network) {
|
public HttpProxyTestContainer(Network network) {
|
||||||
super(
|
super(
|
||||||
new ImageFromDockerfile("es-http-proxy-fixture").withDockerfileFromBuilder(
|
new ImageFromDockerfile("es-http-proxy-fixture").withFileFromClasspath("Dockerfile", "nginx/Dockerfile")
|
||||||
builder -> builder.from(DOCKER_BASE_IMAGE).copy("oidc/nginx.conf", "/etc/nginx/nginx.conf").build()
|
.withFileFromClasspath("nginx/nginx.conf", "/nginx/nginx.conf")
|
||||||
).withFileFromClasspath("oidc/nginx.conf", "/oidc/nginx.conf")
|
|
||||||
);
|
);
|
||||||
addExposedPort(PORT);
|
addExposedPorts(PORT, TLS_PORT);
|
||||||
withNetwork(network);
|
withNetwork(network);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getProxyPort() {
|
public Integer getProxyPort() {
|
||||||
return getMappedPort(PORT);
|
return getMappedPort(PORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getTlsPort() {
|
||||||
|
return getMappedPort(TLS_PORT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import org.testcontainers.images.builder.Transferable;
|
||||||
public final class OidcProviderTestContainer extends DockerEnvironmentAwareTestContainer {
|
public final class OidcProviderTestContainer extends DockerEnvironmentAwareTestContainer {
|
||||||
|
|
||||||
private static final int PORT = 8080;
|
private static final int PORT = 8080;
|
||||||
|
private static final int SSL_PORT = 8443;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* for packer caching only
|
* for packer caching only
|
||||||
|
@ -26,13 +27,14 @@ public final class OidcProviderTestContainer extends DockerEnvironmentAwareTestC
|
||||||
public OidcProviderTestContainer(Network network) {
|
public OidcProviderTestContainer(Network network) {
|
||||||
super(
|
super(
|
||||||
new ImageFromDockerfile("es-oidc-provider-fixture").withFileFromClasspath("oidc/setup.sh", "/oidc/setup.sh")
|
new ImageFromDockerfile("es-oidc-provider-fixture").withFileFromClasspath("oidc/setup.sh", "/oidc/setup.sh")
|
||||||
|
.withFileFromClasspath("oidc/testnode.jks", "/oidc/testnode.jks")
|
||||||
// we cannot make use of docker file builder
|
// we cannot make use of docker file builder
|
||||||
// as it does not support multi-stage builds
|
// as it does not support multi-stage builds
|
||||||
.withFileFromClasspath("Dockerfile", "oidc/Dockerfile")
|
.withFileFromClasspath("Dockerfile", "oidc/Dockerfile")
|
||||||
);
|
);
|
||||||
withNetworkAliases("oidc-provider");
|
withNetworkAliases("oidc-provider");
|
||||||
withNetwork(network);
|
withNetwork(network);
|
||||||
addExposedPort(PORT);
|
addExposedPorts(PORT, SSL_PORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -47,7 +49,6 @@ public final class OidcProviderTestContainer extends DockerEnvironmentAwareTestC
|
||||||
+ getMappedPort(PORT)
|
+ getMappedPort(PORT)
|
||||||
+ "/c2id-login/\n"
|
+ "/c2id-login/\n"
|
||||||
+ "op.reg.apiAccessTokenSHA256=d1c4fa70d9ee708d13cfa01daa0e060a05a2075a53c5cc1ad79e460e96ab5363\n"
|
+ "op.reg.apiAccessTokenSHA256=d1c4fa70d9ee708d13cfa01daa0e060a05a2075a53c5cc1ad79e460e96ab5363\n"
|
||||||
+ "jose.jwkSer=RnVsbCBrZXk6CnsKICAia2V5cyI6IFsKICAgIHsKICAgICAgInAiOiAiLXhhN2d2aW5tY3N3QXU3Vm1mV2loZ2o3U3gzUzhmd2dFSTdMZEVveW5FU1RzcElaeUY5aHc0NVhQZmI5VHlpbzZsOHZTS0F5RmU4T2lOalpkNE1Ra0ttYlJzTmxxR1Y5VlBoWF84UG1JSm5mcGVhb3E5YnZfU0k1blZHUl9zYUUzZE9sTEE2VWpaS0lsRVBNb0ZuRlZCMUFaUU9qQlhRRzZPTDg2eDZ2NHMwIiwKICAgICAgImt0eSI6ICJSU0EiLAogICAgICAicSI6ICJ2Q3pDQUlpdHV0MGx1V0djQloyLUFabURLc1RxNkkxcUp0RmlEYkIyZFBNQVlBNldOWTdaWEZoVWxsSjJrT2ZELWdlYjlkYkN2ODBxNEwyajVZSjZoOTBUc1NRWWVHRlljN1lZMGdCMU5VR3l5cXctb29QN0EtYlJmMGI3b3I4ajZJb0hzQTZKa2JranN6c3otbkJ2U2RmUURlZkRNSVc3Ni1ZWjN0c2hsY2MiLAogICAgICAiZCI6ICJtbFBOcm1zVVM5UmJtX1I5SElyeHdmeFYzZnJ2QzlaQktFZzRzc1ZZaThfY09lSjV2U1hyQV9laEtwa2g4QVhYaUdWUGpQbVlyd29xQzFVUksxUkZmLVg0dG10emV2OUVHaU12Z0JCaEF5RkdTSUd0VUNla2x4Q2dhb3BpMXdZSU1Bd0M0STZwMUtaZURxTVNCWVZGeHA5ZWlJZ2pwb05JbV9lR3hXUUs5VHNnYmk5T3lyc1VqaE9KLVczN2JVMEJWUU56UXpxODhCcGxmNzM3VmV1dy1FeDZaMk1iWXR3SWdfZ0JVb0JEZ0NrZkhoOVE4MElYcEZRV0x1RzgwenFrdkVwTHZ0RWxLbDRvQ3BHVnBjcmFUOFNsOGpYc3FDT1k0dnVRT19LRVUzS2VPNUNJbHd4eEhJYXZjQTE5cHFpSWJ5cm1LbThxS0ZEWHluUFJMSGFNZ1EiLAogICAgICAiZSI6ICJBUUFCIiwKICAgICAgImtpZCI6ICJyc2EzODRfMjA0OCIsCiAgICAgICJxaSI6ICJzMldTamVrVDl3S2JPbk9neGNoaDJPY3VubzE2Y20wS281Z3hoUWJTdVMyMldfUjJBR2ZVdkRieGF0cTRLakQ3THo3X1k2TjdTUkwzUVpudVhoZ1djeXgyNGhrUGppQUZLNmlkYVZKQzJqQmgycEZTUDVTNXZxZ0lsME12eWY4NjlwdkN4S0NzaGRKMGdlRWhveE93VkRPYXJqdTl2Zm9IQV90LWJoRlZrUnciLAogICAgICAiZHAiOiAiQlJhQTFqYVRydG9mTHZBSUJBYW1OSEVhSm51RU9zTVJJMFRCZXFuR1BNUm0tY2RjSG1OUVo5WUtqb2JpdXlmbnhGZ0piVDlSeElBRG0ySkpoZEp5RTN4Y1dTSzhmSjBSM1Jick1aT1dwako0QmJTVzFtU1VtRnlKTGxib3puRFhZR2RaZ1hzS0o1UkFrRUNQZFBCY3YwZVlkbk9NYWhfZndfaFZoNjRuZ2tFIiwKICAgICAgImFsZyI6ICJSU0EzODQiLAogICAgICAiZHEiOiAiUFJoVERKVlR3cDNXaDZfWFZrTjIwMUlpTWhxcElrUDN1UTYyUlRlTDNrQ2ZXSkNqMkZPLTRxcVRIQk0tQjZJWUVPLXpoVWZyQnhiMzJ1djNjS2JDWGFZN3BJSFJxQlFEQWQ2WGhHYzlwc0xqNThXd3VGY2RncERJYUFpRjNyc3NUMjJ4UFVvYkJFTVdBalV3bFJrNEtNTjItMnpLQk5FR3lIcDIzOUpKdnpVIiwKICAgICAgIm4iOiAidUpDWDVDbEZpM0JnTXBvOWhRSVZ2SDh0Vi1jLTVFdG5OeUZxVm91R3NlNWwyUG92MWJGb0tsRllsU25YTzNWUE9KRWR3azNDdl9VT0UtQzlqZERYRHpvS3Z4RURaTVM1TDZWMFpIVEJoNndIOV9iN3JHSlBxLV9RdlNkejczSzZxbHpGaUtQamRvdTF6VlFYTmZfblBZbnRnQkdNRUtBc1pRNGp0cWJCdE5lV0h0MF9UM001cEktTV9KNGVlRWpCTW95TkZuU2ExTEZDVmZRNl9YVnpjelp1TlRGMlh6UmdRWkFmcmJGRXZ6eXR1TzVMZTNTTXFrUUFJeDhFQmkwYXVlRUNqNEQ4cDNVNXFVRG92NEF2VnRJbUZlbFJvb1pBMHJtVW1KRHJ4WExrVkhuVUpzaUF6ZW9TLTNBSnV1bHJkMGpuNjJ5VjZHV2dFWklZMVNlZVd3IgogICAgfQogIF0KfQo\n"
|
|
||||||
+ "op.authz.alwaysPromptForConsent=true\n"
|
+ "op.authz.alwaysPromptForConsent=true\n"
|
||||||
+ "op.authz.alwaysPromptForAuth=true"
|
+ "op.authz.alwaysPromptForAuth=true"
|
||||||
),
|
),
|
||||||
|
@ -63,4 +64,7 @@ public final class OidcProviderTestContainer extends DockerEnvironmentAwareTestC
|
||||||
return getC2OPUrl() + "/c2id";
|
return getC2OPUrl() + "/c2id";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getC2IDSslUrl() {
|
||||||
|
return "https://127.0.0.1:" + getMappedPort(SSL_PORT) + "/c2id";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
37
x-pack/test/idp-fixture/src/main/resources/nginx/Dockerfile
Normal file
37
x-pack/test/idp-fixture/src/main/resources/nginx/Dockerfile
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# We need to do SSL tunnelling, but NGINX doesn't support this out of the box - there's a module for this, but the only
|
||||||
|
# way to install it is to compile NGINX from source, so here we go
|
||||||
|
|
||||||
|
FROM nginx:1.27.1
|
||||||
|
|
||||||
|
RUN set -x \
|
||||||
|
&& apt-get update \
|
||||||
|
# libs and tools that we'll need to build NGINX
|
||||||
|
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||||
|
zlib1g-dev libpcre2-dev libssl-dev wget git gcc patch make \
|
||||||
|
&& wget http://nginx.org/download/nginx-1.27.1.tar.gz \
|
||||||
|
&& git clone --depth 1 --branch v0.0.7 --single-branch https://github.com/chobits/ngx_http_proxy_connect_module.git \
|
||||||
|
&& tar -xzvf nginx-1.27.1.tar.gz \
|
||||||
|
&& cd nginx-1.27.1 \
|
||||||
|
# patch the CONNECT module in
|
||||||
|
&& patch -p1 < ../ngx_http_proxy_connect_module/patch/proxy_connect_rewrite_102101.patch \
|
||||||
|
&& ./configure \
|
||||||
|
--sbin-path=/usr/local/bin/nginx \
|
||||||
|
--conf-path=/etc/nginx/nginx.conf \
|
||||||
|
--pid-path=/etc/nginx/nginx.pid \
|
||||||
|
--with-http_ssl_module \
|
||||||
|
--http-log-path=/dev/stdout \
|
||||||
|
--error-log-path=/dev/stderr \
|
||||||
|
--add-module=../ngx_http_proxy_connect_module \
|
||||||
|
&& make && make install \
|
||||||
|
&& apt-get remove --purge --auto-remove -y && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
|
||||||
|
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
EXPOSE 8888
|
||||||
|
EXPOSE 8889
|
||||||
|
|
||||||
|
STOPSIGNAL SIGQUIT
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
|
@ -14,5 +14,26 @@ http {
|
||||||
proxy_pass http://$ophost;
|
proxy_pass http://$ophost;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
set $ophost "oidc-provider:8443";
|
||||||
|
|
||||||
|
listen 8889;
|
||||||
|
|
||||||
|
# dns resolver used by forward proxying
|
||||||
|
resolver 127.0.0.11;
|
||||||
|
|
||||||
|
# forward proxy for CONNECT request
|
||||||
|
proxy_connect;
|
||||||
|
proxy_connect_allow 1-65535;
|
||||||
|
proxy_connect_connect_timeout 10s;
|
||||||
|
proxy_connect_data_timeout 10s;
|
||||||
|
proxy_connect_address $ophost;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
resolver 127.0.0.11;
|
||||||
|
proxy_pass https://$ophost;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
FROM c2id/c2id-server-demo:12.18 AS c2id
|
FROM c2id/c2id-server-demo:16.1.1 AS c2id
|
||||||
FROM openjdk:11.0.16-jre
|
FROM openjdk:21-jdk-buster
|
||||||
|
|
||||||
# Using this to launch a fake server on container start; see `setup.sh`
|
# Using this to launch a fake server on container start; see `setup.sh`
|
||||||
RUN apt-get update -qqy && apt-get install -qqy python3
|
RUN apt-get update -qqy && apt-get install -qqy python3
|
||||||
|
@ -7,7 +7,28 @@ RUN apt-get update -qqy && apt-get install -qqy python3
|
||||||
COPY --from=c2id /c2id-server /c2id-server
|
COPY --from=c2id /c2id-server /c2id-server
|
||||||
COPY --from=c2id /etc/c2id /etc/c2id
|
COPY --from=c2id /etc/c2id /etc/c2id
|
||||||
COPY ./oidc/setup.sh /fixture/
|
COPY ./oidc/setup.sh /fixture/
|
||||||
|
COPY ./oidc/testnode.jks /c2id-server/tomcat/conf/keystore.jks
|
||||||
|
|
||||||
|
RUN sed -i '/<!-- A "Connector" using the shared thread pool-->/ i\
|
||||||
|
<Connector port="8443" \
|
||||||
|
protocol="org.apache.coyote.http11.Http11NioProtocol" \
|
||||||
|
SSLEnabled="true" \
|
||||||
|
maxThreads="150" \
|
||||||
|
scheme="https" \
|
||||||
|
secure="true" \
|
||||||
|
clientAuth="false" \
|
||||||
|
sslProtocol="TLS" \
|
||||||
|
sslEnabledProtocols="TLSv1.3"> \
|
||||||
|
<SSLHostConfig> \
|
||||||
|
<Certificate \
|
||||||
|
certificateKeystoreFile="/c2id-server/tomcat/conf/keystore.jks" \
|
||||||
|
certificateKeystorePassword="testnode" \
|
||||||
|
type="RSA" /> \
|
||||||
|
</SSLHostConfig> \
|
||||||
|
</Connector>' \
|
||||||
|
/c2id-server/tomcat/conf/server.xml
|
||||||
|
|
||||||
ENV CATALINA_OPTS="-DsystemPropertiesURL=file:///config/c2id/override.properties"
|
ENV CATALINA_OPTS="-DsystemPropertiesURL=file:///config/c2id/override.properties"
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
EXPOSE 8443
|
||||||
CMD ["/bin/bash", "/fixture/setup.sh"]
|
CMD ["/bin/bash", "/fixture/setup.sh"]
|
||||||
|
|
|
@ -7,12 +7,15 @@
|
||||||
|
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# HACK: we start serving on 8080 so that we can progress to the postProcessFixture step. That's the step during which
|
# HACK: we start serving on 8080 & 8443 so that we can progress to the postProcessFixture step. That's the step during which
|
||||||
# we have access to the ephemeral port of the container, which we need to properly configure the issuer field in c2id
|
# we have access to the ephemeral port of the container, which we need to properly configure the issuer field in c2id
|
||||||
# config
|
# config
|
||||||
python3 -m http.server 8080 &
|
python3 -m http.server 8080 &
|
||||||
PY_PID=$!
|
PY_PID=$!
|
||||||
|
|
||||||
|
python3 -m http.server 8443 &
|
||||||
|
PY_PID_2=$!
|
||||||
|
|
||||||
until [ -f /config/c2id/override.properties ]
|
until [ -f /config/c2id/override.properties ]
|
||||||
do
|
do
|
||||||
echo "Waiting for properties file"
|
echo "Waiting for properties file"
|
||||||
|
@ -21,5 +24,5 @@ done
|
||||||
echo "Properties file available. Starting server..."
|
echo "Properties file available. Starting server..."
|
||||||
|
|
||||||
# now that the properties file is configured and available, stop our fake server and launch the real thing
|
# now that the properties file is configured and available, stop our fake server and launch the real thing
|
||||||
kill -SIGKILL $PY_PID
|
kill -SIGKILL $PY_PID $PY_PID_2
|
||||||
bash /c2id-server/tomcat/bin/catalina.sh run
|
bash /c2id-server/tomcat/bin/catalina.sh run
|
||||||
|
|
BIN
x-pack/test/idp-fixture/src/main/resources/oidc/testnode.jks
Normal file
BIN
x-pack/test/idp-fixture/src/main/resources/oidc/testnode.jks
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue