From a9338672c5d4e10150e88a4d56f05cf0a0215373 Mon Sep 17 00:00:00 2001 From: Ioannis Kakavas Date: Wed, 3 Jun 2020 09:36:23 +0300 Subject: [PATCH] Add http proxy support for OIDC realm (#57039) This change introduces support for using an http proxy for egress communication of the OpenID Connect realm. --- .../settings/security-settings.asciidoc | 18 ++++++ .../oidc/OpenIdConnectRealmSettings.java | 48 +++++++++++++- .../oidc/OpenIdConnectAuthenticator.java | 15 ++++- .../authc/oidc/OpenIdConnectRealm.java | 2 +- .../oidc/OpenIdConnectRealmSettingsTests.java | 63 +++++++++++++++++++ x-pack/qa/oidc-op-tests/build.gradle | 36 ++++++++--- .../authc/oidc/OpenIdConnectAuthIT.java | 24 ++++--- x-pack/test/idp-fixture/docker-compose.yml | 11 +++- x-pack/test/idp-fixture/oidc/nginx.conf | 18 ++++++ 9 files changed, 212 insertions(+), 23 deletions(-) create mode 100644 x-pack/test/idp-fixture/oidc/nginx.conf diff --git a/docs/reference/settings/security-settings.asciidoc b/docs/reference/settings/security-settings.asciidoc index 7f97dcec5b994..9dfaded9e5294 100644 --- a/docs/reference/settings/security-settings.asciidoc +++ b/docs/reference/settings/security-settings.asciidoc @@ -1347,6 +1347,24 @@ id tokens with regards to their creation and expiration times. Specifies whether to populate the {es} user's metadata with the values that are provided by the OpenID Connect claims. Defaults to `true`. +`http.proxy.host`:: +Specifies the address of the proxy server that will be used by the internal +http client for all back-channel communication to the OpenID Connect Provider +endpoints. This includes requests to the Token Endpoint, the Userinfo Endpoint +and requests to fetch the JSON Web Key Set from the OP if `op.jwkset_path` is +set as a URL. + +`http.proxy.scheme`:: +Specifies the protocol to use to connect to the proxy server that will be +used by the http client for all back-channel communication to the OpenID +Connect Provider endpoints. Defaults to `http`. Allowed values are +`http` or `https`. + +`http.proxy.port`:: +Specifies the port of the proxy server that will be used by the http +client for all backchannel communication to the OpenID Connect Provider +endpoints. Defaults to `80`. + `http.connect_timeout`:: Controls the behavior of the http client used for back-channel communication to the OpenID Connect Provider endpoints. Specifies the timeout until a connection diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/oidc/OpenIdConnectRealmSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/oidc/OpenIdConnectRealmSettings.java index 90a98d29217dd..55a7a4f4aaff7 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/oidc/OpenIdConnectRealmSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/oidc/OpenIdConnectRealmSettings.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.core.security.authc.oidc; +import org.apache.http.HttpHost; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.unit.TimeValue; @@ -19,7 +20,9 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -139,6 +142,48 @@ private OpenIdConnectRealmSettings() { public static final Setting.AffixSetting HTTP_MAX_ENDPOINT_CONNECTIONS = Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "http.max_endpoint_connections", key -> Setting.intSetting(key, 200, Setting.Property.NodeScope)); + public static final Setting.AffixSetting 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, Object> settings) { + final String namespace = HTTP_PROXY_HOST.getNamespace(HTTP_PROXY_HOST.getConcreteSetting(key)); + final Setting portSetting = HTTP_PROXY_PORT.getConcreteSettingForNamespace(namespace); + final Integer port = (Integer) settings.get(portSetting); + final Setting 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 + public Iterator> settings() { + final String namespace = HTTP_PROXY_HOST.getNamespace(HTTP_PROXY_HOST.getConcreteSetting(key)); + final List> settings = List.of(HTTP_PROXY_PORT.getConcreteSettingForNamespace(namespace), + HTTP_PROXY_SCHEME.getConcreteSettingForNamespace(namespace)); + return settings.iterator(); + } + }, Setting.Property.NodeScope)); + public static final Setting.AffixSetting 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 HTTP_PROXY_SCHEME + = Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "http.proxy.scheme", + key -> Setting.simpleString(key, "http", value -> { + if (value.equals("http") == false && value.equals("https") == false) { + throw new IllegalArgumentException("Invalid value [" + value + "] for [" + key + "]. Only `http` or `https` are allowed."); + } + }, Setting.Property.NodeScope)); public static final ClaimSetting PRINCIPAL_CLAIM = new ClaimSetting("principal"); public static final ClaimSetting GROUPS_CLAIM = new ClaimSetting("groups"); @@ -151,7 +196,8 @@ public static Set> getSettings() { RP_CLIENT_ID, RP_REDIRECT_URI, RP_RESPONSE_TYPE, RP_REQUESTED_SCOPES, RP_CLIENT_SECRET, RP_SIGNATURE_ALGORITHM, RP_POST_LOGOUT_REDIRECT_URI, OP_AUTHORIZATION_ENDPOINT, OP_TOKEN_ENDPOINT, OP_USERINFO_ENDPOINT, OP_ENDSESSION_ENDPOINT, OP_ISSUER, OP_JWKSET_PATH, POPULATE_USER_METADATA, HTTP_CONNECT_TIMEOUT, HTTP_CONNECTION_READ_TIMEOUT, - HTTP_SOCKET_TIMEOUT, HTTP_MAX_CONNECTIONS, HTTP_MAX_ENDPOINT_CONNECTIONS, ALLOWED_CLOCK_SKEW); + HTTP_SOCKET_TIMEOUT, HTTP_MAX_CONNECTIONS, HTTP_MAX_ENDPOINT_CONNECTIONS, HTTP_PROXY_HOST, HTTP_PROXY_PORT, + HTTP_PROXY_SCHEME, ALLOWED_CLOCK_SKEW); set.addAll(DelegatedAuthorizationSettings.getSettings(TYPE)); set.addAll(RealmSettings.getStandardSettings(TYPE)); set.addAll(SSLConfigurationSettings.getRealmSettings(TYPE)); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthenticator.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthenticator.java index 0e4ab4a03ce0c..8fe5c595fca4e 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthenticator.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthenticator.java @@ -42,6 +42,7 @@ import org.apache.commons.codec.Charsets; import org.apache.http.Header; import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.auth.AuthenticationException; @@ -56,6 +57,7 @@ import org.apache.http.entity.ContentType; import org.apache.http.impl.auth.BasicScheme; import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; +import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; import org.apache.http.impl.nio.client.HttpAsyncClients; import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager; import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor; @@ -112,6 +114,9 @@ import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.HTTP_CONNECT_TIMEOUT; import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.HTTP_MAX_CONNECTIONS; import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.HTTP_MAX_ENDPOINT_CONNECTIONS; +import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.HTTP_PROXY_HOST; +import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.HTTP_PROXY_PORT; +import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.HTTP_PROXY_SCHEME; import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.HTTP_SOCKET_TIMEOUT; /** @@ -579,10 +584,14 @@ private CloseableHttpAsyncClient createHttpClient() { .setConnectTimeout(Math.toIntExact(realmConfig.getSetting(HTTP_CONNECT_TIMEOUT).getMillis())) .setConnectionRequestTimeout(Math.toIntExact(realmConfig.getSetting(HTTP_CONNECTION_READ_TIMEOUT).getSeconds())) .setSocketTimeout(Math.toIntExact(realmConfig.getSetting(HTTP_SOCKET_TIMEOUT).getMillis())).build(); - CloseableHttpAsyncClient httpAsyncClient = HttpAsyncClients.custom() + HttpAsyncClientBuilder httpAsyncClientBuilder = HttpAsyncClients.custom() .setConnectionManager(connectionManager) - .setDefaultRequestConfig(requestConfig) - .build(); + .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))); + } + CloseableHttpAsyncClient httpAsyncClient = httpAsyncClientBuilder.build(); httpAsyncClient.start(); return httpAsyncClient; }); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectRealm.java index 8f3f05ea30d39..2189c629a5059 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectRealm.java @@ -257,9 +257,9 @@ private RelyingPartyConfiguration buildRelyingPartyConfiguration(RealmConfig con } final ResponseType responseType; try { - // This should never happen as it's already validated in the settings responseType = ResponseType.parse(require(config, RP_RESPONSE_TYPE)); } catch (ParseException e) { + // This should never happen as it's already validated in the settings throw new SettingsException("Invalid value for " + RP_RESPONSE_TYPE.getKey(), e); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectRealmSettingsTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectRealmSettingsTests.java index cdeb59b79533c..9827be75a2c88 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectRealmSettingsTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectRealmSettingsTests.java @@ -264,6 +264,69 @@ public void testMissingClientSecretThrowsError() { Matchers.containsString(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_CLIENT_SECRET))); } + public void testInvalidProxySchemeThrowsError() { + final Settings.Builder settingsBuilder = Settings.builder() + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_AUTHORIZATION_ENDPOINT), "https://op.example.com/login") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_TOKEN_ENDPOINT), "https://op.example.com/token") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_ISSUER), "https://op.example.com") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_JWKSET_PATH), "https://op.example.com/jwks.json") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.PRINCIPAL_CLAIM.getClaim()), "sub") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_REDIRECT_URI), "https://rp.my.com") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_CLIENT_ID), "rp-my") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_RESPONSE_TYPE), "code") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_PROXY_HOST), "proxyhostname.org") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_PROXY_SCHEME), "invalid"); + settingsBuilder.setSecureSettings(getSecureSettings()); + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> { + buildConfig(settingsBuilder.build()).getSetting(OpenIdConnectRealmSettings.HTTP_PROXY_SCHEME); + }); + assertThat(exception.getMessage(), + Matchers.containsString(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_PROXY_SCHEME))); + } + + public void testInvalidProxyPortThrowsError() { + final Settings.Builder settingsBuilder = Settings.builder() + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_AUTHORIZATION_ENDPOINT), "https://op.example.com/login") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_TOKEN_ENDPOINT), "https://op.example.com/token") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_ISSUER), "https://op.example.com") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_JWKSET_PATH), "https://op.example.com/jwks.json") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.PRINCIPAL_CLAIM.getClaim()), "sub") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_REDIRECT_URI), "https://rp.my.com") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_CLIENT_ID), "rp-my") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_RESPONSE_TYPE), "code") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_PROXY_HOST), "proxyhostname.org") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_PROXY_PORT), 123456); + settingsBuilder.setSecureSettings(getSecureSettings()); + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> { + buildConfig(settingsBuilder.build()).getSetting(OpenIdConnectRealmSettings.HTTP_PROXY_PORT); + }); + assertThat(exception.getMessage(), + Matchers.containsString(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_PROXY_PORT))); + } + + public void testInvalidProxyHostThrowsError() { + final Settings.Builder settingsBuilder = Settings.builder() + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_AUTHORIZATION_ENDPOINT), "https://op.example.com/login") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_TOKEN_ENDPOINT), "https://op.example.com/token") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_ISSUER), "https://op.example.com") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_JWKSET_PATH), "https://op.example.com/jwks.json") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.PRINCIPAL_CLAIM.getClaim()), "sub") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_REDIRECT_URI), "https://rp.my.com") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_CLIENT_ID), "rp-my") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_RESPONSE_TYPE), "code") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_PROXY_HOST), "proxy hostname.org") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_PROXY_PORT), 8080); + settingsBuilder.setSecureSettings(getSecureSettings()); + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> { + buildConfig(settingsBuilder.build()).getSetting(OpenIdConnectRealmSettings.HTTP_PROXY_HOST); + }); + assertThat(exception.getMessage(), Matchers.allOf( + Matchers.containsString(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_PROXY_HOST)), + Matchers.containsString(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_PROXY_PORT)), + Matchers.containsString(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_PROXY_SCHEME)) + )); + } + private MockSecureSettings getSecureSettings() { MockSecureSettings secureSettings = new MockSecureSettings(); secureSettings.setString(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_CLIENT_SECRET), diff --git a/x-pack/qa/oidc-op-tests/build.gradle b/x-pack/qa/oidc-op-tests/build.gradle index 86a53e5487695..7121f10faf2d4 100644 --- a/x-pack/qa/oidc-op-tests/build.gradle +++ b/x-pack/qa/oidc-op-tests/build.gradle @@ -12,13 +12,15 @@ dependencies { } testFixtures.useFixture ":x-pack:test:idp-fixture", "oidc-provider" -String ephemeralPort; +String ephemeralOpPort +String ephemeralProxyPort task setupPorts { // Don't attempt to get ephemeral ports when Docker is not available onlyIf { idpFixtureProject.postProcessFixture.state.skipped == false } dependsOn idpFixtureProject.postProcessFixture doLast { - ephemeralPort = idpFixtureProject.postProcessFixture.ext."test.fixtures.oidc-provider.tcp.8080" + ephemeralOpPort = idpFixtureProject.postProcessFixture.ext."test.fixtures.oidc-provider.tcp.8080" + ephemeralProxyPort = idpFixtureProject.postProcessFixture.ext."test.fixtures.http-proxy.tcp.8888" } } @@ -37,9 +39,9 @@ testClusters.integTest { // OpenID Connect Realm 1 configured for authorization grant flow setting 'xpack.security.authc.realms.oidc.c2id.order', '2' setting 'xpack.security.authc.realms.oidc.c2id.op.issuer', 'http://localhost:8080' - setting 'xpack.security.authc.realms.oidc.c2id.op.authorization_endpoint', { "http://127.0.0.1:${ephemeralPort}/c2id-login" } - setting 'xpack.security.authc.realms.oidc.c2id.op.token_endpoint', { "http://127.0.0.1:${ephemeralPort}/c2id/token" } - setting 'xpack.security.authc.realms.oidc.c2id.op.userinfo_endpoint', { "http://127.0.0.1:${ephemeralPort}/c2id/userinfo" } + setting 'xpack.security.authc.realms.oidc.c2id.op.authorization_endpoint', { "http://127.0.0.1:${ephemeralOpPort}/c2id-login" } + setting 'xpack.security.authc.realms.oidc.c2id.op.token_endpoint', { "http://127.0.0.1:${ephemeralOpPort}/c2id/token" } + setting 'xpack.security.authc.realms.oidc.c2id.op.userinfo_endpoint', { "http://127.0.0.1:${ephemeralOpPort}/c2id/userinfo" } setting 'xpack.security.authc.realms.oidc.c2id.op.jwkset_path', 'op-jwks.json' setting 'xpack.security.authc.realms.oidc.c2id.rp.redirect_uri', 'https://my.fantastic.rp/cb' setting 'xpack.security.authc.realms.oidc.c2id.rp.client_id', 'https://my.elasticsearch.org/rp' @@ -52,9 +54,9 @@ testClusters.integTest { // OpenID Connect Realm 2 configured for implicit flow setting 'xpack.security.authc.realms.oidc.c2id-implicit.order', '3' setting 'xpack.security.authc.realms.oidc.c2id-implicit.op.issuer', 'http://localhost:8080' - setting 'xpack.security.authc.realms.oidc.c2id-implicit.op.authorization_endpoint', { "http://127.0.0.1:${ephemeralPort}/c2id-login" } - setting 'xpack.security.authc.realms.oidc.c2id-implicit.op.token_endpoint', { "http://127.0.0.1:${ephemeralPort}/c2id/token" } - setting 'xpack.security.authc.realms.oidc.c2id-implicit.op.userinfo_endpoint', { "http://127.0.0.1:${ephemeralPort}/c2id/userinfo" } + setting 'xpack.security.authc.realms.oidc.c2id-implicit.op.authorization_endpoint', { "http://127.0.0.1:${ephemeralOpPort}/c2id-login" } + setting 'xpack.security.authc.realms.oidc.c2id-implicit.op.token_endpoint', { "http://127.0.0.1:${ephemeralOpPort}/c2id/token" } + setting 'xpack.security.authc.realms.oidc.c2id-implicit.op.userinfo_endpoint', { "http://127.0.0.1:${ephemeralOpPort}/c2id/userinfo" } setting 'xpack.security.authc.realms.oidc.c2id-implicit.op.jwkset_path', 'op-jwks.json' setting 'xpack.security.authc.realms.oidc.c2id-implicit.rp.redirect_uri', 'https://my.fantastic.rp/cb' setting 'xpack.security.authc.realms.oidc.c2id-implicit.rp.client_id', 'elasticsearch-rp' @@ -64,8 +66,24 @@ testClusters.integTest { setting 'xpack.security.authc.realms.oidc.c2id-implicit.claims.name', 'name' setting 'xpack.security.authc.realms.oidc.c2id-implicit.claims.mail', 'email' setting 'xpack.security.authc.realms.oidc.c2id-implicit.claims.groups', 'groups' + // OpenID Connect Realm 3 configured to use a proxy + setting 'xpack.security.authc.realms.oidc.c2id-proxy.order', '4' + setting 'xpack.security.authc.realms.oidc.c2id-proxy.op.issuer', 'http://localhost:8080' + setting 'xpack.security.authc.realms.oidc.c2id-proxy.op.authorization_endpoint', { "http://127.0.0.1:${ephemeralOpPort}/c2id-login" } + setting 'xpack.security.authc.realms.oidc.c2id-proxy.op.token_endpoint', { "http://127.0.0.1:${ephemeralOpPort}/c2id/token" } + setting 'xpack.security.authc.realms.oidc.c2id-proxy.op.userinfo_endpoint', { "http://127.0.0.1:${ephemeralOpPort}/c2id/userinfo" } + setting 'xpack.security.authc.realms.oidc.c2id-proxy.op.jwkset_path', 'op-jwks.json' + setting 'xpack.security.authc.realms.oidc.c2id-proxy.rp.redirect_uri', 'https://my.fantastic.rp/cb' + setting 'xpack.security.authc.realms.oidc.c2id-proxy.rp.client_id', 'https://my.elasticsearch.org/rp' + keystore 'xpack.security.authc.realms.oidc.c2id-proxy.rp.client_secret', 'b07efb7a1cf6ec9462afe7b6d3ab55c6c7880262aa61ac28dded292aca47c9a2' + setting 'xpack.security.authc.realms.oidc.c2id-proxy.rp.response_type', 'code' + setting 'xpack.security.authc.realms.oidc.c2id-proxy.claims.principal', 'sub' + setting 'xpack.security.authc.realms.oidc.c2id-proxy.claims.name', 'name' + setting 'xpack.security.authc.realms.oidc.c2id-proxy.claims.mail', 'email' + setting 'xpack.security.authc.realms.oidc.c2id-proxy.claims.groups', 'groups' + setting 'xpack.security.authc.realms.oidc.c2id-proxy.http.proxy.host', '127.0.0.1' + setting 'xpack.security.authc.realms.oidc.c2id-proxy.http.proxy.port', {"${ephemeralProxyPort}"} setting 'xpack.ml.enabled', 'false' - extraConfigFile 'op-jwks.json', idpFixtureProject.file("oidc/op-jwks.json") user username: "test_admin", password: "x-pack-test-password" diff --git a/x-pack/qa/oidc-op-tests/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthIT.java b/x-pack/qa/oidc-op-tests/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthIT.java index a082e39ec24d7..6e1dd7c7fdce9 100644 --- a/x-pack/qa/oidc-op-tests/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthIT.java +++ b/x-pack/qa/oidc-op-tests/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthIT.java @@ -64,6 +64,7 @@ public class OpenIdConnectAuthIT extends ESRestTestCase { private static final String REALM_NAME = "c2id"; private static final String REALM_NAME_IMPLICIT = "c2id-implicit"; + private static final String REALM_NAME_PROXY = "c2id-proxy"; private static final String FACILITATOR_PASSWORD = "f@cilit@t0r"; private static final String REGISTRATION_URL = "http://127.0.0.1:" + getEphemeralPortFromProperty("8080") + "/c2id/clients"; private static final String LOGIN_API = "http://127.0.0.1:" + getEphemeralPortFromProperty("8080") + "/c2id-login/api/"; @@ -244,22 +245,29 @@ private void configureJsonRequest(HttpEntityEnclosingRequestBase request, String public void testAuthenticateWithCodeFlow() throws Exception { final PrepareAuthResponse prepareAuthResponse = getRedirectedFromFacilitator(REALM_NAME); final String redirectUri = authenticateAtOP(prepareAuthResponse.getAuthUri()); - final String realm = randomBoolean() ? null : prepareAuthResponse.getRealm(); Tuple tokens = completeAuthentication(redirectUri, prepareAuthResponse.getState(), - prepareAuthResponse.getNonce(), realm); + prepareAuthResponse.getNonce(), REALM_NAME); verifyElasticsearchAccessTokenForCodeFlow(tokens.v1()); } public void testAuthenticateWithImplicitFlow() throws Exception { final PrepareAuthResponse prepareAuthResponse = getRedirectedFromFacilitator(REALM_NAME_IMPLICIT); final String redirectUri = authenticateAtOP(prepareAuthResponse.getAuthUri()); - final String realm = randomBoolean() ? null : prepareAuthResponse.getRealm(); Tuple tokens = completeAuthentication(redirectUri, prepareAuthResponse.getState(), - prepareAuthResponse.getNonce(), realm); + prepareAuthResponse.getNonce(), REALM_NAME_IMPLICIT); verifyElasticsearchAccessTokenForImplicitFlow(tokens.v1()); } + public void testAuthenticateWithCodeFlowUsingHttpProxy() throws Exception { + final PrepareAuthResponse prepareAuthResponse = getRedirectedFromFacilitator(REALM_NAME_PROXY); + final String redirectUri = authenticateAtOP(prepareAuthResponse.getAuthUri()); + + Tuple tokens = completeAuthentication(redirectUri, prepareAuthResponse.getState(), + prepareAuthResponse.getNonce(), REALM_NAME_PROXY); + verifyElasticsearchAccessTokenForCodeFlow(tokens.v1()); + } + public void testAuthenticateWithCodeFlowFailsForWrongRealm() throws Exception { final PrepareAuthResponse prepareAuthResponse = getRedirectedFromFacilitator(REALM_NAME); final String redirectUri = authenticateAtOP(prepareAuthResponse.getAuthUri()); @@ -377,7 +385,10 @@ private void setRoleMappings() throws IOException { createRoleMappingRequest.setJsonEntity("{ \"roles\" : [\"kibana_admin\"]," + "\"enabled\": true," + "\"rules\": {" + - "\"field\": { \"realm.name\": \"" + REALM_NAME + "\"}" + + " \"any\" : [" + + " {\"field\": { \"realm.name\": \"" + REALM_NAME + "\"} }," + + " {\"field\": { \"realm.name\": \"" + REALM_NAME_PROXY + "\"} }" + + " ]" + "}" + "}"); adminClient().performRequest(createRoleMappingRequest); @@ -409,13 +420,11 @@ class PrepareAuthResponse { private URI authUri; private String state; private String nonce; - private String realm; PrepareAuthResponse(URI authUri, String state, String nonce, @Nullable String realm) { this.authUri = authUri; this.state = state; this.nonce = nonce; - this.realm = realm; } URI getAuthUri() { @@ -430,6 +439,5 @@ String getNonce() { return nonce; } - String getRealm() { return realm;} } } diff --git a/x-pack/test/idp-fixture/docker-compose.yml b/x-pack/test/idp-fixture/docker-compose.yml index c549fbbfa5dd7..3b46e05552a83 100644 --- a/x-pack/test/idp-fixture/docker-compose.yml +++ b/x-pack/test/idp-fixture/docker-compose.yml @@ -41,7 +41,16 @@ services: oidc-provider: image: "c2id/c2id-server:7.8" + depends_on: + - http-proxy ports: - "8080" volumes: - - ./oidc/override.properties:/etc/c2id/override.properties \ No newline at end of file + - ./oidc/override.properties:/etc/c2id/override.properties + + http-proxy: + image: "nginx:latest" + volumes: + - ./oidc/nginx.conf:/etc/nginx/nginx.conf + ports: + - "8888" diff --git a/x-pack/test/idp-fixture/oidc/nginx.conf b/x-pack/test/idp-fixture/oidc/nginx.conf new file mode 100644 index 0000000000000..3146af9811447 --- /dev/null +++ b/x-pack/test/idp-fixture/oidc/nginx.conf @@ -0,0 +1,18 @@ +worker_processes 1; +events { + worker_connections 1024; +} +http { + server { + # Acts as a simple forward http proxy for the OIDC realm configuration + listen 8888; + location / { + # oidc-provider is another container so we need to rewrite `127.0.0.1:ephemeralPort` with `oidc-provider:8080` + # so that nginx can access that + resolver 127.0.0.11; + set $ophost "oidc-provider:8080"; + proxy_pass http://$ophost; + } + } +} +