From 13491e09f9caca9adbd5c3d0bb3fd09f4ea34d98 Mon Sep 17 00:00:00 2001 From: Ioannis Kakavas Date: Mon, 14 Sep 2020 09:16:23 +0300 Subject: [PATCH] Oidc additional client auth types (#58708) The OpenID Connect specification defines a number of ways for a client (RP) to authenticate itself to the OP when accessing the Token Endpoint. We currently only support `client_secret_basic`. This change introduces support for 2 additional authentication methods, namely `client_secret_post` (where the client credentials are passed in the body of the POST request to the OP) and `client_secret_jwt` where the client constructs a JWT and signs it using the the client secret as a key. Support for the above, and especially `client_secret_jwt` in our integration tests meant that the OP we use ( Connect2id server ) should be able to validate the JWT that we send it from the RP. Since we run the OP in docker and it listens on an ephemeral port we would have no way of knowing the port so that we can configure the ES running via the testcluster to know the "correct" Token Endpoint, and even if we did, this would not be the Token Endpoint URL that the OP would think it listens on. To alleviate this, we run an ES single node cluster in docker, alongside the OP so that we can configured it with the correct hostname and port within the docker network. --- .../oidc/OpenIdConnectRealmSettings.java | 34 ++- .../oidc/OpenIdConnectAuthenticator.java | 32 ++- .../authc/oidc/OpenIdConnectRealm.java | 9 +- .../authc/oidc/RelyingPartyConfiguration.java | 18 +- .../oidc/OpenIdConnectAuthenticatorTests.java | 8 + .../oidc/OpenIdConnectRealmSettingsTests.java | 78 +++++- x-pack/qa/oidc-op-tests/build.gradle | 78 +----- .../authc/oidc/OpenIdConnectAuthIT.java | 253 ++++++++++++------ .../src/test/resources/tls/testnode.jks | Bin 0 -> 9903 bytes x-pack/test/idp-fixture/build.gradle | 37 ++- x-pack/test/idp-fixture/docker-compose.yml | 123 ++++++++- .../idp-fixture/docker-test-entrypoint.sh | 12 + .../test/idp-fixture/oidc/override.properties | 6 +- 13 files changed, 507 insertions(+), 181 deletions(-) create mode 100644 x-pack/qa/oidc-op-tests/src/test/resources/tls/testnode.jks create mode 100755 x-pack/test/idp-fixture/docker-test-entrypoint.sh 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 99408da80057f..3e3d081f5a16d 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 @@ -32,9 +32,15 @@ public class OpenIdConnectRealmSettings { private OpenIdConnectRealmSettings() { } - private static final List SUPPORTED_SIGNATURE_ALGORITHMS = Collections.unmodifiableList( - Arrays.asList("HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "PS256", "PS384", "PS512")); - private static final List RESPONSE_TYPES = Arrays.asList("code", "id_token", "id_token token"); + public static final List SUPPORTED_SIGNATURE_ALGORITHMS = + org.elasticsearch.common.collect.List.of( + "HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "PS256", "PS384", "PS512"); + private static final List RESPONSE_TYPES = org.elasticsearch.common.collect.List.of( + "code", "id_token", "id_token token"); + public static final List CLIENT_AUTH_METHODS = org.elasticsearch.common.collect.List.of( + "client_secret_basic", "client_secret_post", "client_secret_jwt"); + public static final List SUPPORTED_CLIENT_AUTH_JWT_ALGORITHMS = org.elasticsearch.common.collect.List.of( + "HS256", "HS384", "HS512"); public static final String TYPE = "oidc"; public static final Setting.AffixSetting RP_CLIENT_ID @@ -78,7 +84,22 @@ private OpenIdConnectRealmSettings() { public static final Setting.AffixSetting> RP_REQUESTED_SCOPES = Setting.affixKeySetting( RealmSettings.realmSettingPrefix(TYPE), "rp.requested_scopes", key -> Setting.listSetting(key, Collections.singletonList("openid"), Function.identity(), Setting.Property.NodeScope)); - + public static final Setting.AffixSetting RP_CLIENT_AUTH_METHOD + = Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "rp.client_auth_method", + key -> new Setting<>(key, "client_secret_basic", Function.identity(), v -> { + if (CLIENT_AUTH_METHODS.contains(v) == false) { + throw new IllegalArgumentException( + "Invalid value [" + v + "] for [" + key + "]. Allowed values are " + CLIENT_AUTH_METHODS + "}]"); + } + }, Setting.Property.NodeScope)); + public static final Setting.AffixSetting RP_CLIENT_AUTH_JWT_SIGNATURE_ALGORITHM + = Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "rp.client_auth_jwt_signature_algorithm", + key -> new Setting<>(key, "HS384", Function.identity(), v -> { + if (SUPPORTED_CLIENT_AUTH_JWT_ALGORITHMS.contains(v) == false) { + throw new IllegalArgumentException( + "Invalid value [" + v + "] for [" + key + "]. Allowed values are " + SUPPORTED_CLIENT_AUTH_JWT_ALGORITHMS + "}]"); + } + }, Setting.Property.NodeScope)); public static final Setting.AffixSetting OP_AUTHORIZATION_ENDPOINT = Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "op.authorization_endpoint", key -> Setting.simpleString(key, v -> { @@ -194,8 +215,9 @@ public Iterator> settings() { public static Set> getSettings() { final Set> set = Sets.newHashSet( 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, + RP_POST_LOGOUT_REDIRECT_URI, RP_CLIENT_AUTH_METHOD, RP_CLIENT_AUTH_JWT_SIGNATURE_ALGORITHM, 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, HTTP_PROXY_HOST, HTTP_PROXY_PORT, HTTP_PROXY_SCHEME, ALLOWED_CLOCK_SKEW); set.addAll(DelegatedAuthorizationSettings.getSettings(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 deaebb689215e..332b29d3c5605 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 @@ -22,6 +22,8 @@ import com.nimbusds.oauth2.sdk.ErrorObject; import com.nimbusds.oauth2.sdk.ResponseType; import com.nimbusds.oauth2.sdk.TokenErrorResponse; +import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod; +import com.nimbusds.oauth2.sdk.auth.ClientSecretJWT; import com.nimbusds.oauth2.sdk.auth.Secret; import com.nimbusds.oauth2.sdk.id.State; import com.nimbusds.oauth2.sdk.token.AccessToken; @@ -85,6 +87,7 @@ import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.security.authc.RealmConfig; import org.elasticsearch.xpack.core.security.authc.RealmSettings; +import org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings; import org.elasticsearch.xpack.core.ssl.SSLConfiguration; import org.elasticsearch.xpack.core.ssl.SSLService; @@ -463,19 +466,36 @@ private void exchangeCodeForToken(AuthorizationCode code, ActionListener params = new ArrayList<>(); for (Map.Entry> entry : codeGrant.toParameters().entrySet()) { // All parameters of AuthorizationCodeGrant are singleton lists params.add(new BasicNameValuePair(entry.getKey(), entry.getValue().get(0))); } + if (rpConfig.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)) { + UsernamePasswordCredentials creds = + new UsernamePasswordCredentials(URLEncoder.encode(rpConfig.getClientId().getValue(), StandardCharsets.UTF_8.name()), + URLEncoder.encode(rpConfig.getClientSecret().toString(), StandardCharsets.UTF_8.name())); + httpPost.addHeader(new BasicScheme().authenticate(creds, httpPost, null)); + } else if (rpConfig.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.CLIENT_SECRET_POST)) { + params.add(new BasicNameValuePair("client_id", rpConfig.getClientId().getValue())); + params.add(new BasicNameValuePair("client_secret", rpConfig.getClientSecret().toString())); + } else if (rpConfig.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.CLIENT_SECRET_JWT)) { + ClientSecretJWT clientSecretJWT = new ClientSecretJWT(rpConfig.getClientId(), opConfig.getTokenEndpoint(), + rpConfig.getClientAuthenticationJwtAlgorithm(), new Secret(rpConfig.getClientSecret().toString())); + for (Map.Entry> entry : clientSecretJWT.toParameters().entrySet()) { + // Both client_assertion and client_assertion_type are singleton lists + params.add(new BasicNameValuePair(entry.getKey(), entry.getValue().get(0))); + } + } else { + tokensListener.onFailure(new ElasticsearchSecurityException("Failed to exchange code for Id Token using Token Endpoint." + + "Expected client authentication method to be one of " + OpenIdConnectRealmSettings.CLIENT_AUTH_METHODS + + " but was [" + rpConfig.getClientAuthenticationMethod() + "]")); + } httpPost.setEntity(new UrlEncodedFormEntity(params)); - httpPost.setHeader("Content-type", "application/x-www-form-urlencoded"); - UsernamePasswordCredentials creds = - new UsernamePasswordCredentials(URLEncoder.encode(rpConfig.getClientId().getValue(), StandardCharsets.UTF_8.name()), - URLEncoder.encode(rpConfig.getClientSecret().toString(), StandardCharsets.UTF_8.name())); - httpPost.addHeader(new BasicScheme().authenticate(creds, httpPost, null)); SpecialPermission.check(); AccessController.doPrivileged((PrivilegedAction) () -> { + httpClient.execute(httpPost, new FutureCallback() { @Override public void completed(HttpResponse result) { @@ -496,7 +516,7 @@ public void cancelled() { }); return null; }); - } catch (AuthenticationException | UnsupportedEncodingException e) { + } catch (AuthenticationException | UnsupportedEncodingException | JOSEException e) { tokensListener.onFailure( new ElasticsearchSecurityException("Failed to exchange code for Id Token using the Token Endpoint.", e)); } 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 4cbd35af7a7ef..01ad9503548d1 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 @@ -12,6 +12,7 @@ import com.nimbusds.oauth2.sdk.ParseException; import com.nimbusds.oauth2.sdk.ResponseType; import com.nimbusds.oauth2.sdk.Scope; +import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod; import com.nimbusds.oauth2.sdk.id.ClientID; import com.nimbusds.oauth2.sdk.id.Issuer; import com.nimbusds.oauth2.sdk.id.State; @@ -73,6 +74,8 @@ import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.OP_USERINFO_ENDPOINT; import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.POPULATE_USER_METADATA; import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.PRINCIPAL_CLAIM; +import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.RP_CLIENT_AUTH_JWT_SIGNATURE_ALGORITHM; +import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.RP_CLIENT_AUTH_METHOD; import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.RP_CLIENT_ID; import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.RP_CLIENT_SECRET; import static org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings.RP_POST_LOGOUT_REDIRECT_URI; @@ -266,9 +269,11 @@ private RelyingPartyConfiguration buildRelyingPartyConfiguration(RealmConfig con requestedScope.add("openid"); } final JWSAlgorithm signatureAlgorithm = JWSAlgorithm.parse(require(config, RP_SIGNATURE_ALGORITHM)); - + final ClientAuthenticationMethod clientAuthenticationMethod = + ClientAuthenticationMethod.parse(require(config, RP_CLIENT_AUTH_METHOD)); + final JWSAlgorithm clientAuthJwtAlgorithm = JWSAlgorithm.parse(require(config, RP_CLIENT_AUTH_JWT_SIGNATURE_ALGORITHM)); return new RelyingPartyConfiguration(clientId, clientSecret, redirectUri, responseType, requestedScope, - signatureAlgorithm, postLogoutRedirectUri); + signatureAlgorithm, clientAuthenticationMethod, clientAuthJwtAlgorithm, postLogoutRedirectUri); } private OpenIdConnectProviderConfiguration buildOpenIdConnectProviderConfiguration(RealmConfig config) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/RelyingPartyConfiguration.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/RelyingPartyConfiguration.java index ed67974c0b0d2..deae6fcfff011 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/RelyingPartyConfiguration.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/RelyingPartyConfiguration.java @@ -8,6 +8,7 @@ import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.oauth2.sdk.ResponseType; import com.nimbusds.oauth2.sdk.Scope; +import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod; import com.nimbusds.oauth2.sdk.id.ClientID; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.settings.SecureString; @@ -26,15 +27,22 @@ public class RelyingPartyConfiguration { private final Scope requestedScope; private final JWSAlgorithm signatureAlgorithm; private final URI postLogoutRedirectUri; + private final ClientAuthenticationMethod clientAuthenticationMethod; + private final JWSAlgorithm clientAuthenticationJwtAlgorithm; public RelyingPartyConfiguration(ClientID clientId, SecureString clientSecret, URI redirectUri, ResponseType responseType, - Scope requestedScope, JWSAlgorithm algorithm, @Nullable URI postLogoutRedirectUri) { + Scope requestedScope, JWSAlgorithm algorithm, ClientAuthenticationMethod clientAuthenticationMethod, + JWSAlgorithm clientAuthenticationJwtAlgorithm, @Nullable URI postLogoutRedirectUri) { this.clientId = Objects.requireNonNull(clientId, "clientId must be provided"); this.clientSecret = Objects.requireNonNull(clientSecret, "clientSecret must be provided"); this.redirectUri = Objects.requireNonNull(redirectUri, "redirectUri must be provided"); this.responseType = Objects.requireNonNull(responseType, "responseType must be provided"); this.requestedScope = Objects.requireNonNull(requestedScope, "responseType must be provided"); this.signatureAlgorithm = Objects.requireNonNull(algorithm, "algorithm must be provided"); + this.clientAuthenticationMethod = Objects.requireNonNull(clientAuthenticationMethod, + "clientAuthenticationMethod must be provided"); + this.clientAuthenticationJwtAlgorithm = Objects.requireNonNull(clientAuthenticationJwtAlgorithm, + "clientAuthenticationJwtAlgorithm must be provided"); this.postLogoutRedirectUri = postLogoutRedirectUri; } @@ -65,4 +73,12 @@ public JWSAlgorithm getSignatureAlgorithm() { public URI getPostLogoutRedirectUri() { return postLogoutRedirectUri; } + + public ClientAuthenticationMethod getClientAuthenticationMethod() { + return clientAuthenticationMethod; + } + + public JWSAlgorithm getClientAuthenticationJwtAlgorithm() { + return clientAuthenticationJwtAlgorithm; + } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthenticatorTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthenticatorTests.java index fa13700264dc9..d3642286e7d3c 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthenticatorTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthenticatorTests.java @@ -27,6 +27,7 @@ import com.nimbusds.jwt.proc.BadJWTException; import com.nimbusds.oauth2.sdk.ResponseType; import com.nimbusds.oauth2.sdk.Scope; +import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod; import com.nimbusds.oauth2.sdk.auth.Secret; import com.nimbusds.oauth2.sdk.id.ClientID; import com.nimbusds.oauth2.sdk.id.Issuer; @@ -892,8 +893,11 @@ private RelyingPartyConfiguration getDefaultRpConfig() throws URISyntaxException new ResponseType("id_token", "token"), new Scope("openid"), JWSAlgorithm.RS384, + ClientAuthenticationMethod.CLIENT_SECRET_BASIC, + JWSAlgorithm.HS384, new URI("https://rp.elastic.co/successfull_logout")); } + private RelyingPartyConfiguration getRpConfig(String alg) throws URISyntaxException { return new RelyingPartyConfiguration( new ClientID("rp-my"), @@ -902,6 +906,8 @@ private RelyingPartyConfiguration getRpConfig(String alg) throws URISyntaxExcept new ResponseType("id_token", "token"), new Scope("openid"), JWSAlgorithm.parse(alg), + ClientAuthenticationMethod.CLIENT_SECRET_BASIC, + JWSAlgorithm.HS384, new URI("https://rp.elastic.co/successfull_logout")); } @@ -913,6 +919,8 @@ private RelyingPartyConfiguration getRpConfigNoAccessToken(String alg) throws UR new ResponseType("id_token"), new Scope("openid"), JWSAlgorithm.parse(alg), + ClientAuthenticationMethod.CLIENT_SECRET_BASIC, + JWSAlgorithm.HS384, new URI("https://rp.elastic.co/successfull_logout")); } 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 db47e6deb8c24..a72bff7445242 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 @@ -33,6 +33,43 @@ public void setupEnv() { threadContext = new ThreadContext(globalSettings); } + public void testAllSettings() { + 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_USERINFO_ENDPOINT), "https://op.example.com/userinfo") + .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.POPULATE_USER_METADATA), randomBoolean()) + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.PRINCIPAL_CLAIM.getClaim()), "sub") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.GROUPS_CLAIM.getClaim()), "group1") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.DN_CLAIM.getClaim()), "uid=sub,ou=people,dc=example,dc=com") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.NAME_CLAIM.getClaim()), "name") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.MAIL_CLAIM.getClaim()), "e@mail.com") + .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.RP_REQUESTED_SCOPES), "openid") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_SIGNATURE_ALGORITHM), + randomFrom(OpenIdConnectRealmSettings.SUPPORTED_SIGNATURE_ALGORITHMS)) + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_POST_LOGOUT_REDIRECT_URI), "https://my.rp.com/logout") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_CLIENT_AUTH_METHOD), + randomFrom(OpenIdConnectRealmSettings.CLIENT_AUTH_METHODS)) + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_CLIENT_AUTH_JWT_SIGNATURE_ALGORITHM), + randomFrom(OpenIdConnectRealmSettings.SUPPORTED_CLIENT_AUTH_JWT_ALGORITHMS)) + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_CONNECT_TIMEOUT), "5s") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_CONNECTION_READ_TIMEOUT), "5s") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_SOCKET_TIMEOUT), "5s") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_MAX_CONNECTIONS), "5") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_MAX_ENDPOINT_CONNECTIONS), "5") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_PROXY_HOST), "host") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_PROXY_PORT), "8080") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.HTTP_PROXY_SCHEME), "http") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.ALLOWED_CLOCK_SKEW), "10s"); + settingsBuilder.setSecureSettings(getSecureSettings()); + new OpenIdConnectRealm(buildConfig(settingsBuilder.build()), null, null); + } + public void testIncorrectResponseTypeThrowsError() { final Settings.Builder settingsBuilder = Settings.builder() .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_AUTHORIZATION_ENDPOINT), "https://op.example.com/login") @@ -115,10 +152,8 @@ public void testMissingTokenEndpointIsAllowedInImplicitFlow() { settingsBuilder.setSecureSettings(getSecureSettings()); final OpenIdConnectRealm realm = new OpenIdConnectRealm(buildConfig(settingsBuilder.build()), null, null); assertNotNull(realm); - } - public void testInvalidTokenEndpointThrowsError() { final Settings.Builder settingsBuilder = Settings.builder() .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_AUTHORIZATION_ENDPOINT), "https://op.example.com/login") @@ -326,6 +361,45 @@ public void testInvalidProxyHostThrowsError() { )); } + public void testInvalidClientAuthenticationMethodThrowsError() { + 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_ISSUER), "https://op.example.com") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_JWKSET_PATH), "https://op.example.com/jwks.json") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_TOKEN_ENDPOINT), "https://op.example.com/token") + .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_CLIENT_AUTH_METHOD), "none") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_RESPONSE_TYPE), "code"); + settingsBuilder.setSecureSettings(getSecureSettings()); + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> { + new OpenIdConnectRealm(buildConfig(settingsBuilder.build()), null, null); + }); + assertThat(exception.getMessage(), + Matchers.containsString(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_CLIENT_AUTH_METHOD))); + } + + public void testInvalidClientAuthenticationJwtAlgorithmThrowsError() { + 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_ISSUER), "https://op.example.com") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_JWKSET_PATH), "https://op.example.com/jwks.json") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.OP_TOKEN_ENDPOINT), "https://op.example.com/token") + .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.RP_CLIENT_AUTH_METHOD), "client_secret_jwt") + .put(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_CLIENT_AUTH_JWT_SIGNATURE_ALGORITHM), "AB234"); + settingsBuilder.setSecureSettings(getSecureSettings()); + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> { + new OpenIdConnectRealm(buildConfig(settingsBuilder.build()), null, null); + }); + assertThat(exception.getMessage(), + Matchers.containsString(getFullSettingKey(REALM_NAME, OpenIdConnectRealmSettings.RP_CLIENT_AUTH_JWT_SIGNATURE_ALGORITHM))); + } + 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 8e95504ec35a7..dec75d293c592 100644 --- a/x-pack/qa/oidc-op-tests/build.gradle +++ b/x-pack/qa/oidc-op-tests/build.gradle @@ -11,82 +11,6 @@ dependencies { testImplementation project(path: xpackModule('security'), configuration: 'testArtifacts') } testFixtures.useFixture ":x-pack:test:idp-fixture", "oidc-provider" - -String ephemeralOpPort -String ephemeralProxyPort -tasks.register("setupPorts") { - // Don't attempt to get ephemeral ports when Docker is not available - onlyIf { idpFixtureProject.postProcessFixture.state.skipped == false } - dependsOn idpFixtureProject.postProcessFixture - doLast { - ephemeralOpPort = idpFixtureProject.postProcessFixture.ext."test.fixtures.oidc-provider.tcp.8080" - ephemeralProxyPort = idpFixtureProject.postProcessFixture.ext."test.fixtures.http-proxy.tcp.8888" - } -} - -integTest { - dependsOn "setupPorts" -} - -testClusters.integTest { - testDistribution = 'DEFAULT' - setting 'xpack.license.self_generated.type', 'trial' - setting 'xpack.security.enabled', 'true' - setting 'xpack.security.http.ssl.enabled', 'false' - setting 'xpack.security.authc.token.enabled', 'true' - setting 'xpack.security.authc.realms.file.file.order', '0' - setting 'xpack.security.authc.realms.native.native.order', '1' - // 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:${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' - keystore 'xpack.security.authc.realms.oidc.c2id.rp.client_secret', 'b07efb7a1cf6ec9462afe7b6d3ab55c6c7880262aa61ac28dded292aca47c9a2' - setting 'xpack.security.authc.realms.oidc.c2id.rp.response_type', 'code' - setting 'xpack.security.authc.realms.oidc.c2id.claims.principal', 'sub' - setting 'xpack.security.authc.realms.oidc.c2id.claims.name', 'name' - setting 'xpack.security.authc.realms.oidc.c2id.claims.mail', 'email' - setting 'xpack.security.authc.realms.oidc.c2id.claims.groups', 'groups' - // 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:${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' - keystore 'xpack.security.authc.realms.oidc.c2id-implicit.rp.client_secret', 'b07efb7a1cf6ec9462afe7b6d3ab55c6c7880262aa61ac28dded292aca47c9a2' - setting 'xpack.security.authc.realms.oidc.c2id-implicit.rp.response_type', 'id_token token' - setting 'xpack.security.authc.realms.oidc.c2id-implicit.claims.principal', 'sub' - 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" -} +testFixtures.useFixture ":x-pack:test:idp-fixture", "elasticsearch-node" thirdPartyAudit.enabled = false 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 6e1dd7c7fdce9..b2fd3907dbd00 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 @@ -9,6 +9,7 @@ import net.minidev.json.parser.JSONParser; import org.apache.http.Header; import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.config.RequestConfig; @@ -29,10 +30,12 @@ import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.Response; import org.elasticsearch.client.ResponseException; +import org.elasticsearch.client.RestClient; import org.elasticsearch.common.CheckedFunction; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; @@ -47,8 +50,11 @@ import org.junit.Before; import org.junit.BeforeClass; +import java.io.FileNotFoundException; import java.io.IOException; import java.net.URI; +import java.net.URL; +import java.nio.file.Path; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -65,16 +71,34 @@ 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 REALM_NAME_CLIENT_POST_AUTH = "c2id-post"; + private static final String REALM_NAME_CLIENT_JWT_AUTH = "c2id-jwt"; 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/"; + private static final String REGISTRATION_URL = "http://127.0.0.1:" + getEphemeralTcpPortFromProperty("oidc-provider", "8080") + + "/c2id/clients"; + private static final String LOGIN_API = "http://127.0.0.1:" + getEphemeralTcpPortFromProperty("oidc-provider", "8080") + + "/c2id-login/api/"; + private static final String CLIENT_SECRET = "b07efb7a1cf6ec9462afe7b6d3ab55c6c7880262aa61ac28dded292aca47c9a2"; + // SHA256 of this is defined in x-pack/test/idp-fixture/oidc/override.properties + private static final String OP_API_BEARER_TOKEN = "811fa888f3e0fdc9e01d4201bfeee46a"; + private static final String ES_PORT = getEphemeralTcpPortFromProperty("elasticsearch-node", "9200"); + private static Path HTTP_TRUSTSTORE; @Before - public void setupUserAndRoles() throws IOException { + public void setupUserAndRoles() throws Exception { setFacilitatorUser(); setRoleMappings(); } + @BeforeClass + public static void readTrustStore() throws Exception { + final URL resource = OpenIdConnectAuthIT.class.getResource("/tls/testnode.jks"); + if (resource == null) { + throw new FileNotFoundException("Cannot find classpath resource /tls/testnode.jks"); + } + HTTP_TRUSTSTORE = PathUtils.get(resource.toURI()); + } + /** * C2id server only supports dynamic registration, so we can't pre-seed it's config with our client data. Execute only once */ @@ -85,40 +109,82 @@ public static void registerClients() throws Exception { "\"grant_types\": [\"authorization_code\"]," + "\"response_types\": [\"code\"]," + "\"preferred_client_id\":\"https://my.elasticsearch.org/rp\"," + - "\"preferred_client_secret\":\"b07efb7a1cf6ec9462afe7b6d3ab55c6c7880262aa61ac28dded292aca47c9a2\"," + - "\"redirect_uris\": [\"https://my.fantastic.rp/cb\"]" + + "\"preferred_client_secret\":\"" + CLIENT_SECRET + "\"," + + "\"redirect_uris\": [\"https://my.fantastic.rp/cb\"]," + + "\"token_endpoint_auth_method\":\"client_secret_basic\"" + "}"; String implicitClient = "{" + "\"grant_types\": [\"implicit\"]," + "\"response_types\": [\"token id_token\"]," + "\"preferred_client_id\":\"elasticsearch-rp\"," + - "\"preferred_client_secret\":\"b07efb7a1cf6ec9462afe7b6d3ab55c6c7880262aa61ac28dded292aca47c9a2\"," + + "\"preferred_client_secret\":\"" + CLIENT_SECRET + "\"," + "\"redirect_uris\": [\"https://my.fantastic.rp/cb\"]" + "}"; + String postClient = "{" + + "\"grant_types\": [\"authorization_code\"]," + + "\"response_types\": [\"code\"]," + + "\"preferred_client_id\":\"elasticsearch-post\"," + + "\"preferred_client_secret\":\"" + CLIENT_SECRET + "\"," + + "\"redirect_uris\": [\"https://my.fantastic.rp/cb\"]," + + "\"token_endpoint_auth_method\":\"client_secret_post\"" + + "}"; + String jwtClient = "{" + + "\"grant_types\": [\"authorization_code\"]," + + "\"response_types\": [\"code\"]," + + "\"preferred_client_id\":\"elasticsearch-post-jwt\"," + + "\"preferred_client_secret\":\"" + CLIENT_SECRET + "\"," + + "\"redirect_uris\": [\"https://my.fantastic.rp/cb\"]," + + "\"token_endpoint_auth_method\":\"client_secret_jwt\"" + + "}"; HttpPost httpPost = new HttpPost(REGISTRATION_URL); final BasicHttpContext context = new BasicHttpContext(); httpPost.setEntity(new StringEntity(codeClient, ContentType.APPLICATION_JSON)); httpPost.setHeader("Accept", "application/json"); httpPost.setHeader("Content-type", "application/json"); - httpPost.setHeader("Authorization", "Bearer 811fa888f3e0fdc9e01d4201bfeee46a"); - CloseableHttpResponse response = SocketAccess.doPrivileged(() -> httpClient.execute(httpPost, context)); - assertThat(response.getStatusLine().getStatusCode(), equalTo(200)); - httpPost.setEntity(new StringEntity(implicitClient, ContentType.APPLICATION_JSON)); + httpPost.setHeader("Authorization", "Bearer " + OP_API_BEARER_TOKEN); + HttpPost httpPost2 = new HttpPost(REGISTRATION_URL); httpPost2.setEntity(new StringEntity(implicitClient, ContentType.APPLICATION_JSON)); httpPost2.setHeader("Accept", "application/json"); httpPost2.setHeader("Content-type", "application/json"); - httpPost2.setHeader("Authorization", "Bearer 811fa888f3e0fdc9e01d4201bfeee46a"); - CloseableHttpResponse response2 = SocketAccess.doPrivileged(() -> httpClient.execute(httpPost2, context)); - assertThat(response2.getStatusLine().getStatusCode(), equalTo(200)); + httpPost2.setHeader("Authorization", "Bearer " + OP_API_BEARER_TOKEN); + + HttpPost httpPost3 = new HttpPost(REGISTRATION_URL); + httpPost3.setEntity(new StringEntity(postClient, ContentType.APPLICATION_JSON)); + httpPost3.setHeader("Accept", "application/json"); + httpPost3.setHeader("Content-type", "application/json"); + httpPost3.setHeader("Authorization", "Bearer " + OP_API_BEARER_TOKEN); + + HttpPost httpPost4 = new HttpPost(REGISTRATION_URL); + httpPost4.setEntity(new StringEntity(jwtClient, ContentType.APPLICATION_JSON)); + httpPost4.setHeader("Accept", "application/json"); + httpPost4.setHeader("Content-type", "application/json"); + httpPost4.setHeader("Authorization", "Bearer " + OP_API_BEARER_TOKEN); + + SocketAccess.doPrivileged(() -> { + try (CloseableHttpResponse response = httpClient.execute(httpPost, context)) { + assertThat(response.getStatusLine().getStatusCode(), equalTo(201)); + } + try (CloseableHttpResponse response2 = httpClient.execute(httpPost2, context)) { + assertThat(response2.getStatusLine().getStatusCode(), equalTo(201)); + } + try (CloseableHttpResponse response3 = httpClient.execute(httpPost3, context)) { + assertThat(response3.getStatusLine().getStatusCode(), equalTo(201)); + } + try (CloseableHttpResponse response4 = httpClient.execute(httpPost4, context)) { + assertThat(response4.getStatusLine().getStatusCode(), equalTo(201)); + } + }); } } @Override protected Settings restAdminSettings() { - String token = basicAuthHeaderValue("test_admin", new SecureString("x-pack-test-password".toCharArray())); + String token = basicAuthHeaderValue("x_pack_rest_user", new SecureString("x-pack-test-password".toCharArray())); return Settings.builder() .put(ThreadContext.PREFIX + ".Authorization", token) + .put(TRUSTSTORE_PATH, HTTP_TRUSTSTORE) + .put(TRUSTSTORE_PASSWORD, "testnode") .build(); } @@ -193,19 +259,21 @@ private String authenticateAtOP(URI opAuthUri) throws Exception { } } - private static String getEphemeralPortFromProperty(String port) { - String key = "test.fixtures.oidc-provider.tcp." + port; + private static String getEphemeralTcpPortFromProperty(String service, String port) { + String key = "test.fixtures." + service + ".tcp." + port; final String value = System.getProperty(key); assertNotNull("Expected the actual value for port " + port + " to be in system property " + key, value); return value; } - private Map callAuthenticateApiUsingAccessToken(String accessToken) throws IOException { + private Map callAuthenticateApiUsingAccessToken(String accessToken) throws Exception { Request request = new Request("GET", "/_security/_authenticate"); RequestOptions.Builder options = request.getOptions().toBuilder(); options.addHeader("Authorization", "Bearer " + accessToken); request.setOptions(options); - return entityAsMap(client().performRequest(request)); + try (RestClient restClient = getClient()) { + return entityAsMap(restClient.performRequest(request)); + } } private T execute(CloseableHttpClient client, HttpEntityEnclosingRequestBase request, @@ -250,10 +318,25 @@ public void testAuthenticateWithCodeFlow() throws Exception { verifyElasticsearchAccessTokenForCodeFlow(tokens.v1()); } + public void testAuthenticateWithCodeFlowAndClientPost() throws Exception { + final PrepareAuthResponse prepareAuthResponse = getRedirectedFromFacilitator(REALM_NAME_CLIENT_POST_AUTH); + final String redirectUri = authenticateAtOP(prepareAuthResponse.getAuthUri()); + Tuple tokens = completeAuthentication(redirectUri, prepareAuthResponse.getState(), + prepareAuthResponse.getNonce(), REALM_NAME_CLIENT_POST_AUTH); + verifyElasticsearchAccessTokenForCodeFlow(tokens.v1()); + } + + public void testAuthenticateWithCodeFlowAndClientJwtPost() throws Exception { + final PrepareAuthResponse prepareAuthResponse = getRedirectedFromFacilitator(REALM_NAME_CLIENT_JWT_AUTH); + final String redirectUri = authenticateAtOP(prepareAuthResponse.getAuthUri()); + Tuple tokens = completeAuthentication(redirectUri, prepareAuthResponse.getState(), + prepareAuthResponse.getNonce(), REALM_NAME_CLIENT_JWT_AUTH); + verifyElasticsearchAccessTokenForCodeFlow(tokens.v1()); + } + public void testAuthenticateWithImplicitFlow() throws Exception { final PrepareAuthResponse prepareAuthResponse = getRedirectedFromFacilitator(REALM_NAME_IMPLICIT); final String redirectUri = authenticateAtOP(prepareAuthResponse.getAuthUri()); - Tuple tokens = completeAuthentication(redirectUri, prepareAuthResponse.getState(), prepareAuthResponse.getNonce(), REALM_NAME_IMPLICIT); verifyElasticsearchAccessTokenForImplicitFlow(tokens.v1()); @@ -280,7 +363,7 @@ public void testAuthenticateWithCodeFlowFailsForWrongRealm() throws Exception { assertThat(401, equalTo(e.getResponse().getStatusLine().getStatusCode())); } - private void verifyElasticsearchAccessTokenForCodeFlow(String accessToken) throws IOException { + private void verifyElasticsearchAccessTokenForCodeFlow(String accessToken) throws Exception { final Map map = callAuthenticateApiUsingAccessToken(accessToken); logger.info("Authentication with token Response: " + map); assertThat(map.get("username"), equalTo("alice")); @@ -289,10 +372,10 @@ private void verifyElasticsearchAccessTokenForCodeFlow(String accessToken) throw assertThat(map.get("metadata"), instanceOf(Map.class)); final Map metadata = (Map) map.get("metadata"); assertThat(metadata.get("oidc(sub)"), equalTo("alice")); - assertThat(metadata.get("oidc(iss)"), equalTo("http://localhost:8080")); + assertThat(metadata.get("oidc(iss)"), equalTo("http://oidc-provider:8080/c2id")); } - private void verifyElasticsearchAccessTokenForImplicitFlow(String accessToken) throws IOException { + private void verifyElasticsearchAccessTokenForImplicitFlow(String accessToken) throws Exception { final Map map = callAuthenticateApiUsingAccessToken(accessToken); logger.info("Authentication with token Response: " + map); assertThat(map.get("username"), equalTo("alice")); @@ -301,22 +384,23 @@ private void verifyElasticsearchAccessTokenForImplicitFlow(String accessToken) t assertThat(map.get("metadata"), instanceOf(Map.class)); final Map metadata = (Map) map.get("metadata"); assertThat(metadata.get("oidc(sub)"), equalTo("alice")); - assertThat(metadata.get("oidc(iss)"), equalTo("http://localhost:8080")); + assertThat(metadata.get("oidc(iss)"), equalTo("http://oidc-provider:8080/c2id")); } - private PrepareAuthResponse getRedirectedFromFacilitator(String realmName) throws Exception { final Map body = Collections.singletonMap("realm", realmName); - Request request = buildRequest("POST", "/_security/oidc/prepare", body, facilitatorAuth()); - final Response prepare = client().performRequest(request); - assertOK(prepare); - final Map responseBody = parseResponseAsMap(prepare.getEntity()); - logger.info("Created OpenIDConnect authentication request {}", responseBody); - final String state = (String) responseBody.get("state"); - final String nonce = (String) responseBody.get("nonce"); - final String authUri = (String) responseBody.get("redirect"); - final String realm = (String) responseBody.get("realm"); - return new PrepareAuthResponse(new URI(authUri), state, nonce, realm); + Request request = buildRequest("POST", "/_security/oidc/prepare?error_trace=true", body, facilitatorAuth()); + try (RestClient restClient = getClient()) { + final Response prepare = restClient.performRequest(request); + assertOK(prepare); + final Map responseBody = parseResponseAsMap(prepare.getEntity()); + logger.info("Created OpenIDConnect authentication request {}", responseBody); + final String state = (String) responseBody.get("state"); + final String nonce = (String) responseBody.get("nonce"); + final String authUri = (String) responseBody.get("redirect"); + final String realm = (String) responseBody.get("realm"); + return new PrepareAuthResponse(new URI(authUri), state, nonce, realm); + } } private Tuple completeAuthentication(String redirectUri, String state, String nonce, @Nullable String realm) @@ -325,17 +409,19 @@ private Tuple completeAuthentication(String redirectUri, String body.put("redirect_uri", redirectUri); body.put("state", state); body.put("nonce", nonce); - if (realm != null){ + if (realm != null) { body.put("realm", realm); } Request request = buildRequest("POST", "/_security/oidc/authenticate", body, facilitatorAuth()); - final Response authenticate = client().performRequest(request); - assertOK(authenticate); - final Map responseBody = parseResponseAsMap(authenticate.getEntity()); - logger.info(" OpenIDConnect authentication response {}", responseBody); - assertNotNull(responseBody.get("access_token")); - assertNotNull(responseBody.get("refresh_token")); - return Tuple.tuple(responseBody.get("access_token").toString(), responseBody.get("refresh_token").toString()); + try (RestClient restClient = getClient()) { + final Response authenticate = restClient.performRequest(request); + assertOK(authenticate); + final Map responseBody = parseResponseAsMap(authenticate.getEntity()); + logger.info(" OpenIDConnect authentication response {}", responseBody); + assertNotNull(responseBody.get("access_token")); + assertNotNull(responseBody.get("refresh_token")); + return Tuple.tuple(responseBody.get("access_token").toString(), responseBody.get("refresh_token").toString()); + } } private Request buildRequest(String method, String endpoint, Map body, Header... headers) throws IOException { @@ -371,47 +457,56 @@ private void assertHttpOk(StatusLine status) { * We create a user named `facilitator` with the appropriate privileges ( `manage_oidc` ). A facilitator web app * would need to create one also, in order to access the OIDC related APIs on behalf of the user. */ - private void setFacilitatorUser() throws IOException { - Request createRoleRequest = new Request("PUT", "/_security/role/facilitator"); - createRoleRequest.setJsonEntity("{ \"cluster\" : [\"manage_oidc\", \"manage_token\"] }"); - adminClient().performRequest(createRoleRequest); - Request createUserRequest = new Request("PUT", "/_security/user/facilitator"); - createUserRequest.setJsonEntity("{ \"password\" : \"" + FACILITATOR_PASSWORD + "\", \"roles\" : [\"facilitator\"] }"); - adminClient().performRequest(createUserRequest); + private void setFacilitatorUser() throws Exception { + try (RestClient restClient = getClient()) { + Request createRoleRequest = new Request("PUT", "/_security/role/facilitator"); + createRoleRequest.setJsonEntity("{ \"cluster\" : [\"manage_oidc\", \"manage_token\"] }"); + restClient.performRequest(createRoleRequest); + Request createUserRequest = new Request("PUT", "/_security/user/facilitator"); + createUserRequest.setJsonEntity("{ \"password\" : \"" + FACILITATOR_PASSWORD + "\", \"roles\" : [\"facilitator\"] }"); + restClient.performRequest(createUserRequest); + } } - private void setRoleMappings() throws IOException { - Request createRoleMappingRequest = new Request("PUT", "/_security/role_mapping/oidc_kibana"); - createRoleMappingRequest.setJsonEntity("{ \"roles\" : [\"kibana_admin\"]," + - "\"enabled\": true," + - "\"rules\": {" + - " \"any\" : [" + - " {\"field\": { \"realm.name\": \"" + REALM_NAME + "\"} }," + - " {\"field\": { \"realm.name\": \"" + REALM_NAME_PROXY + "\"} }" + - " ]" + - "}" + - "}"); - adminClient().performRequest(createRoleMappingRequest); - - createRoleMappingRequest = new Request("PUT", "/_security/role_mapping/oidc_limited"); - createRoleMappingRequest.setJsonEntity("{ \"roles\" : [\"limited_user\"]," + - "\"enabled\": true," + - "\"rules\": {" + - "\"field\": { \"realm.name\": \"" + REALM_NAME_IMPLICIT + "\"}" + - "}" + - "}"); - adminClient().performRequest(createRoleMappingRequest); - - createRoleMappingRequest = new Request("PUT", "/_security/role_mapping/oidc_auditor"); - createRoleMappingRequest.setJsonEntity("{ \"roles\" : [\"auditor\"]," + - "\"enabled\": true," + - "\"rules\": {" + - "\"field\": { \"groups\": \"audit\"}" + - "}" + - "}"); - adminClient().performRequest(createRoleMappingRequest); + private void setRoleMappings() throws Exception { + try (RestClient restClient = getClient()) { + Request createRoleMappingRequest = new Request("PUT", "/_security/role_mapping/oidc_kibana"); + createRoleMappingRequest.setJsonEntity("{ \"roles\" : [\"kibana_admin\"]," + + "\"enabled\": true," + + "\"rules\": {" + + " \"any\" : [" + + " {\"field\": { \"realm.name\": \"" + REALM_NAME + "\"} }," + + " {\"field\": { \"realm.name\": \"" + REALM_NAME_PROXY + "\"} }," + + " {\"field\": { \"realm.name\": \"" + REALM_NAME_CLIENT_POST_AUTH + "\"} }," + + " {\"field\": { \"realm.name\": \"" + REALM_NAME_CLIENT_JWT_AUTH + "\"} }" + + " ]" + + "}" + + "}"); + restClient.performRequest(createRoleMappingRequest); + + createRoleMappingRequest = new Request("PUT", "/_security/role_mapping/oidc_limited"); + createRoleMappingRequest.setJsonEntity("{ \"roles\" : [\"limited_user\"]," + + "\"enabled\": true," + + "\"rules\": {" + + "\"field\": { \"realm.name\": \"" + REALM_NAME_IMPLICIT + "\"}" + + "}" + + "}"); + restClient.performRequest(createRoleMappingRequest); + + createRoleMappingRequest = new Request("PUT", "/_security/role_mapping/oidc_auditor"); + createRoleMappingRequest.setJsonEntity("{ \"roles\" : [\"auditor\"]," + + "\"enabled\": true," + + "\"rules\": {" + + "\"field\": { \"groups\": \"audit\"}" + + "}" + + "}"); + restClient.performRequest(createRoleMappingRequest); + } } + private RestClient getClient() throws Exception { + return buildClient(restAdminSettings(), new HttpHost[]{new HttpHost("localhost", Integer.parseInt(ES_PORT), "https")}); + } /** * Simple POJO encapsulating a response to calling /_security/oidc/prepare diff --git a/x-pack/qa/oidc-op-tests/src/test/resources/tls/testnode.jks b/x-pack/qa/oidc-op-tests/src/test/resources/tls/testnode.jks new file mode 100644 index 0000000000000000000000000000000000000000..7c0c4f1aae7fb00ae4f1323d9a51367b615e3e3c GIT binary patch literal 9903 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 literal 0 HcmV?d00001 diff --git a/x-pack/test/idp-fixture/build.gradle b/x-pack/test/idp-fixture/build.gradle index c55123e08d0f1..cb9e49d7d693e 100644 --- a/x-pack/test/idp-fixture/build.gradle +++ b/x-pack/test/idp-fixture/build.gradle @@ -1,4 +1,37 @@ -apply plugin: 'elasticsearch.build' +import org.elasticsearch.gradle.VersionProperties +import org.elasticsearch.gradle.Architecture + apply plugin: 'elasticsearch.test.fixtures' +apply plugin: 'elasticsearch.internal-distribution-download' + +task copyKeystore(type: Sync) { + from project(':x-pack:plugin:core') + .file('src/test/resources/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks') + into "${buildDir}/certs" + doLast { + file("${buildDir}/certs").setReadable(true, false) + file("${buildDir}/certs/testnode.jks").setReadable(true, false) + } +} + +elasticsearch_distributions { + docker { + type = 'docker' + architecture = Architecture.current() + flavor = System.getProperty('tests.distribution', 'default') + version = VersionProperties.getElasticsearch() + failIfUnavailable = false // This ensures we skip this testing if Docker is unavailable + } +} +preProcessFixture { + dependsOn copyKeystore, elasticsearch_distributions.docker + doLast { + File file = file("${buildDir}/logs/node1") + file.mkdirs() + file.setWritable(true, false) + } +} -test.enabled = false \ No newline at end of file +tasks.named('composeUp').configure { + dependsOn "preProcessFixture" +} diff --git a/x-pack/test/idp-fixture/docker-compose.yml b/x-pack/test/idp-fixture/docker-compose.yml index 281badacf3a88..5c6bc0762217e 100644 --- a/x-pack/test/idp-fixture/docker-compose.yml +++ b/x-pack/test/idp-fixture/docker-compose.yml @@ -1,8 +1,121 @@ -version: '3.1' +version: '3.7' services: + elasticsearch-node: + image: elasticsearch:test + environment: + - node.name=elasticsearch-node + - cluster.initial_master_nodes=elasticsearch-node + - cluster.name=elasticsearch-node + - bootstrap.memory_lock=true + - network.publish_host=127.0.0.1 + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + - path.repo=/tmp/es-repo + - node.attr.testattr=test + - cluster.routing.allocation.disk.watermark.low=1b + - cluster.routing.allocation.disk.watermark.high=1b + - cluster.routing.allocation.disk.watermark.flood_stage=1b + - node.store.allow_mmap=false + - xpack.license.self_generated.type=trial + - xpack.security.enabled=true + - xpack.security.http.ssl.enabled=true + - xpack.security.http.ssl.keystore.path=testnode.jks + - xpack.security.authc.token.enabled=true + - xpack.security.authc.realms.file.file.order=0 + - xpack.security.authc.realms.native.native.order=1 + - xpack.security.authc.realms.oidc.c2id.order=2 + - xpack.security.authc.realms.oidc.c2id.op.issuer=http://oidc-provider:8080/c2id + - xpack.security.authc.realms.oidc.c2id.op.authorization_endpoint=http://oidc-provider:8080/c2id-login + - xpack.security.authc.realms.oidc.c2id.op.token_endpoint=http://oidc-provider:8080/c2id/token + - xpack.security.authc.realms.oidc.c2id.op.userinfo_endpoint=http://oidc-provider:8080/c2id/userinfo + - xpack.security.authc.realms.oidc.c2id.op.jwkset_path=op-jwks.json + - xpack.security.authc.realms.oidc.c2id.rp.redirect_uri=https://my.fantastic.rp/cb + - xpack.security.authc.realms.oidc.c2id.rp.client_id=https://my.elasticsearch.org/rp + - xpack.security.authc.realms.oidc.c2id.rp.response_type=code + - xpack.security.authc.realms.oidc.c2id.claims.principal=sub + - xpack.security.authc.realms.oidc.c2id.claims.name=name + - xpack.security.authc.realms.oidc.c2id.claims.mail=email + - xpack.security.authc.realms.oidc.c2id.claims.groups=groups + - xpack.security.authc.realms.oidc.c2id-implicit.order=3 + - xpack.security.authc.realms.oidc.c2id-implicit.op.issuer=http://oidc-provider:8080/c2id + - xpack.security.authc.realms.oidc.c2id-implicit.op.authorization_endpoint=http://oidc-provider:8080/c2id-login + - xpack.security.authc.realms.oidc.c2id-implicit.op.token_endpoint=http://oidc-provider:8080/c2id/token + - xpack.security.authc.realms.oidc.c2id-implicit.op.userinfo_endpoint=http://oidc-provider:8080/c2id/userinfo + - xpack.security.authc.realms.oidc.c2id-implicit.op.jwkset_path=op-jwks.json + - xpack.security.authc.realms.oidc.c2id-implicit.rp.redirect_uri=https://my.fantastic.rp/cb + - xpack.security.authc.realms.oidc.c2id-implicit.rp.client_id=elasticsearch-rp + - xpack.security.authc.realms.oidc.c2id-implicit.rp.response_type=id_token token + - xpack.security.authc.realms.oidc.c2id-implicit.claims.principal=sub + - xpack.security.authc.realms.oidc.c2id-implicit.claims.name=name + - xpack.security.authc.realms.oidc.c2id-implicit.claims.mail=email + - xpack.security.authc.realms.oidc.c2id-implicit.claims.groups=groups + - xpack.security.authc.realms.oidc.c2id-proxy.order=4 + - xpack.security.authc.realms.oidc.c2id-proxy.op.issuer=http://oidc-provider:8080/c2id + - xpack.security.authc.realms.oidc.c2id-proxy.op.authorization_endpoint=http://oidc-provider:8080/c2id-login + - xpack.security.authc.realms.oidc.c2id-proxy.op.token_endpoint=http://oidc-provider:8080/c2id/token + - xpack.security.authc.realms.oidc.c2id-proxy.op.userinfo_endpoint=http://oidc-provider:8080/c2id/userinfo + - xpack.security.authc.realms.oidc.c2id-proxy.op.jwkset_path=op-jwks.json + - xpack.security.authc.realms.oidc.c2id-proxy.rp.redirect_uri=https://my.fantastic.rp/cb + - xpack.security.authc.realms.oidc.c2id-proxy.rp.client_id=https://my.elasticsearch.org/rp + - xpack.security.authc.realms.oidc.c2id-proxy.rp.response_type=code + - xpack.security.authc.realms.oidc.c2id-proxy.claims.principal=sub + - xpack.security.authc.realms.oidc.c2id-proxy.claims.name=name + - xpack.security.authc.realms.oidc.c2id-proxy.claims.mail=email + - xpack.security.authc.realms.oidc.c2id-proxy.claims.groups=groups + - xpack.security.authc.realms.oidc.c2id-proxy.http.proxy.host=http-proxy + - xpack.security.authc.realms.oidc.c2id-proxy.http.proxy.port=8888 + - xpack.security.authc.realms.oidc.c2id-post.order=5 + - xpack.security.authc.realms.oidc.c2id-post.op.issuer=http://oidc-provider:8080/c2id + - xpack.security.authc.realms.oidc.c2id-post.op.authorization_endpoint=http://oidc-provider:8080/c2id-login + - xpack.security.authc.realms.oidc.c2id-post.op.token_endpoint=http://oidc-provider:8080/c2id/token + - xpack.security.authc.realms.oidc.c2id-post.op.userinfo_endpoint=http://oidc-provider:8080/c2id/userinfo + - xpack.security.authc.realms.oidc.c2id-post.op.jwkset_path=op-jwks.json + - xpack.security.authc.realms.oidc.c2id-post.rp.redirect_uri=https://my.fantastic.rp/cb + - xpack.security.authc.realms.oidc.c2id-post.rp.client_id=elasticsearch-post + - xpack.security.authc.realms.oidc.c2id-post.rp.client_auth_method=client_secret_post + - xpack.security.authc.realms.oidc.c2id-post.rp.response_type=code + - xpack.security.authc.realms.oidc.c2id-post.claims.principal=sub + - xpack.security.authc.realms.oidc.c2id-post.claims.name=name + - xpack.security.authc.realms.oidc.c2id-post.claims.mail=email + - xpack.security.authc.realms.oidc.c2id-post.claims.groups=groups + - xpack.security.authc.realms.oidc.c2id-jwt.order=6 + - xpack.security.authc.realms.oidc.c2id-jwt.op.issuer=http://oidc-provider:8080/c2id + - xpack.security.authc.realms.oidc.c2id-jwt.op.authorization_endpoint=http://oidc-provider:8080/c2id-login + - xpack.security.authc.realms.oidc.c2id-jwt.op.token_endpoint=http://oidc-provider:8080/c2id/token + - xpack.security.authc.realms.oidc.c2id-jwt.op.userinfo_endpoint=http://oidc-provider:8080/c2id/userinfo + - xpack.security.authc.realms.oidc.c2id-jwt.op.jwkset_path=op-jwks.json + - xpack.security.authc.realms.oidc.c2id-jwt.rp.redirect_uri=https://my.fantastic.rp/cb + - xpack.security.authc.realms.oidc.c2id-jwt.rp.client_id=elasticsearch-post-jwt + - xpack.security.authc.realms.oidc.c2id-jwt.rp.client_auth_method=client_secret_jwt + - xpack.security.authc.realms.oidc.c2id-jwt.rp.response_type=code + - xpack.security.authc.realms.oidc.c2id-jwt.claims.principal=sub + - xpack.security.authc.realms.oidc.c2id-jwt.claims.name=name + - xpack.security.authc.realms.oidc.c2id-jwt.claims.mail=email + - xpack.security.authc.realms.oidc.c2id-jwt.claims.groups=groups + volumes: + - ./build/logs/node1:/usr/share/elasticsearch/logs + - ./build/certs/testnode.jks:/usr/share/elasticsearch/config/testnode.jks + - ./docker-test-entrypoint.sh:/docker-test-entrypoint.sh + - ./oidc/op-jwks.json:/usr/share/elasticsearch/config/op-jwks.json + ports: + - "9200" + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 65536 + hard: 65536 + entrypoint: /docker-test-entrypoint.sh + healthcheck: + start_period: 15s + test: ["CMD", "curl", "-f", "-u", "x_pack_rest_user:x-pack-test-password", "-k", "https://localhost:9200"] + interval: 10s + timeout: 2s + retries: 5 + openldap: command: --copy-service --loglevel debug - image: "osixia/openldap:1.2.3" + image: "osixia/openldap:1.4.0" ports: - "389" - "636" @@ -41,11 +154,13 @@ services: - ./idp/shib-jetty-base/start.d/ssl.ini:/opt/shib-jetty-base/start.d/ssl.ini oidc-provider: - image: "c2id/c2id-server:7.8" + image: "c2id/c2id-server:9.5" depends_on: - http-proxy ports: - "8080" + expose: + - "8080" volumes: - ./oidc/override.properties:/etc/c2id/override.properties @@ -55,3 +170,5 @@ services: - ./oidc/nginx.conf:/etc/nginx/nginx.conf ports: - "8888" + expose: + - "8888" diff --git a/x-pack/test/idp-fixture/docker-test-entrypoint.sh b/x-pack/test/idp-fixture/docker-test-entrypoint.sh new file mode 100755 index 0000000000000..b0309360e89c5 --- /dev/null +++ b/x-pack/test/idp-fixture/docker-test-entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/bash +cd /usr/share/elasticsearch/bin/ +./elasticsearch-users useradd x_pack_rest_user -p x-pack-test-password -r superuser || true +echo "testnode" >/tmp/password +echo "b07efb7a1cf6ec9462afe7b6d3ab55c6c7880262aa61ac28dded292aca47c9a2" >/tmp/client_secret +cat /tmp/password | ./elasticsearch-keystore add -x -f -v 'xpack.security.http.ssl.keystore.secure_password' +cat /tmp/client_secret | ./elasticsearch-keystore add -x -f -v 'xpack.security.authc.realms.oidc.c2id.rp.client_secret' +cat /tmp/client_secret | ./elasticsearch-keystore add -x -f -v 'xpack.security.authc.realms.oidc.c2id-implicit.rp.client_secret' +cat /tmp/client_secret | ./elasticsearch-keystore add -x -f -v 'xpack.security.authc.realms.oidc.c2id-proxy.rp.client_secret' +cat /tmp/client_secret | ./elasticsearch-keystore add -x -f -v 'xpack.security.authc.realms.oidc.c2id-post.rp.client_secret' +cat /tmp/client_secret | ./elasticsearch-keystore add -x -f -v 'xpack.security.authc.realms.oidc.c2id-jwt.rp.client_secret' +/usr/local/bin/docker-entrypoint.sh | tee >/usr/share/elasticsearch/logs/console.log diff --git a/x-pack/test/idp-fixture/oidc/override.properties b/x-pack/test/idp-fixture/oidc/override.properties index 888bde9acb48e..fe4ba4a6e894f 100644 --- a/x-pack/test/idp-fixture/oidc/override.properties +++ b/x-pack/test/idp-fixture/oidc/override.properties @@ -1,4 +1,4 @@ -op.issuer=http://localhost:8080 -op.authz.endpoint=http://localhost:8080/c2id-login/ +op.issuer=http://oidc-provider:8080/c2id +op.authz.endpoint=http://oidc-provider:8080/c2id-login/ op.reg.apiAccessTokenSHA256=d1c4fa70d9ee708d13cfa01daa0e060a05a2075a53c5cc1ad79e460e96ab5363 -jose.jwkSer=RnVsbCBrZXk6CnsKICAia2V5cyI6IFsKICAgIHsKICAgICAgInAiOiAiLXhhN2d2aW5tY3N3QXU3Vm1mV2loZ2o3U3gzUzhmd2dFSTdMZEVveW5FU1RzcElaeUY5aHc0NVhQZmI5VHlpbzZsOHZTS0F5RmU4T2lOalpkNE1Ra0ttYlJzTmxxR1Y5VlBoWF84UG1JSm5mcGVhb3E5YnZfU0k1blZHUl9zYUUzZE9sTEE2VWpaS0lsRVBNb0ZuRlZCMUFaUU9qQlhRRzZPTDg2eDZ2NHMwIiwKICAgICAgImt0eSI6ICJSU0EiLAogICAgICAicSI6ICJ2Q3pDQUlpdHV0MGx1V0djQloyLUFabURLc1RxNkkxcUp0RmlEYkIyZFBNQVlBNldOWTdaWEZoVWxsSjJrT2ZELWdlYjlkYkN2ODBxNEwyajVZSjZoOTBUc1NRWWVHRlljN1lZMGdCMU5VR3l5cXctb29QN0EtYlJmMGI3b3I4ajZJb0hzQTZKa2JranN6c3otbkJ2U2RmUURlZkRNSVc3Ni1ZWjN0c2hsY2MiLAogICAgICAiZCI6ICJtbFBOcm1zVVM5UmJtX1I5SElyeHdmeFYzZnJ2QzlaQktFZzRzc1ZZaThfY09lSjV2U1hyQV9laEtwa2g4QVhYaUdWUGpQbVlyd29xQzFVUksxUkZmLVg0dG10emV2OUVHaU12Z0JCaEF5RkdTSUd0VUNla2x4Q2dhb3BpMXdZSU1Bd0M0STZwMUtaZURxTVNCWVZGeHA5ZWlJZ2pwb05JbV9lR3hXUUs5VHNnYmk5T3lyc1VqaE9KLVczN2JVMEJWUU56UXpxODhCcGxmNzM3VmV1dy1FeDZaMk1iWXR3SWdfZ0JVb0JEZ0NrZkhoOVE4MElYcEZRV0x1RzgwenFrdkVwTHZ0RWxLbDRvQ3BHVnBjcmFUOFNsOGpYc3FDT1k0dnVRT19LRVUzS2VPNUNJbHd4eEhJYXZjQTE5cHFpSWJ5cm1LbThxS0ZEWHluUFJMSGFNZ1EiLAogICAgICAiZSI6ICJBUUFCIiwKICAgICAgImtpZCI6ICJyc2EzODRfMjA0OCIsCiAgICAgICJxaSI6ICJzMldTamVrVDl3S2JPbk9neGNoaDJPY3VubzE2Y20wS281Z3hoUWJTdVMyMldfUjJBR2ZVdkRieGF0cTRLakQ3THo3X1k2TjdTUkwzUVpudVhoZ1djeXgyNGhrUGppQUZLNmlkYVZKQzJqQmgycEZTUDVTNXZxZ0lsME12eWY4NjlwdkN4S0NzaGRKMGdlRWhveE93VkRPYXJqdTl2Zm9IQV90LWJoRlZrUnciLAogICAgICAiZHAiOiAiQlJhQTFqYVRydG9mTHZBSUJBYW1OSEVhSm51RU9zTVJJMFRCZXFuR1BNUm0tY2RjSG1OUVo5WUtqb2JpdXlmbnhGZ0piVDlSeElBRG0ySkpoZEp5RTN4Y1dTSzhmSjBSM1Jick1aT1dwako0QmJTVzFtU1VtRnlKTGxib3puRFhZR2RaZ1hzS0o1UkFrRUNQZFBCY3YwZVlkbk9NYWhfZndfaFZoNjRuZ2tFIiwKICAgICAgImFsZyI6ICJSU0EzODQiLAogICAgICAiZHEiOiAiUFJoVERKVlR3cDNXaDZfWFZrTjIwMUlpTWhxcElrUDN1UTYyUlRlTDNrQ2ZXSkNqMkZPLTRxcVRIQk0tQjZJWUVPLXpoVWZyQnhiMzJ1djNjS2JDWGFZN3BJSFJxQlFEQWQ2WGhHYzlwc0xqNThXd3VGY2RncERJYUFpRjNyc3NUMjJ4UFVvYkJFTVdBalV3bFJrNEtNTjItMnpLQk5FR3lIcDIzOUpKdnpVIiwKICAgICAgIm4iOiAidUpDWDVDbEZpM0JnTXBvOWhRSVZ2SDh0Vi1jLTVFdG5OeUZxVm91R3NlNWwyUG92MWJGb0tsRllsU25YTzNWUE9KRWR3azNDdl9VT0UtQzlqZERYRHpvS3Z4RURaTVM1TDZWMFpIVEJoNndIOV9iN3JHSlBxLV9RdlNkejczSzZxbHpGaUtQamRvdTF6VlFYTmZfblBZbnRnQkdNRUtBc1pRNGp0cWJCdE5lV0h0MF9UM001cEktTV9KNGVlRWpCTW95TkZuU2ExTEZDVmZRNl9YVnpjelp1TlRGMlh6UmdRWkFmcmJGRXZ6eXR1TzVMZTNTTXFrUUFJeDhFQmkwYXVlRUNqNEQ4cDNVNXFVRG92NEF2VnRJbUZlbFJvb1pBMHJtVW1KRHJ4WExrVkhuVUpzaUF6ZW9TLTNBSnV1bHJkMGpuNjJ5VjZHV2dFWklZMVNlZVd3IgogICAgfQogIF0KfQo \ No newline at end of file +jose.jwkSer=RnVsbCBrZXk6CnsKICAia2V5cyI6IFsKICAgIHsKICAgICAgInAiOiAiLXhhN2d2aW5tY3N3QXU3Vm1mV2loZ2o3U3gzUzhmd2dFSTdMZEVveW5FU1RzcElaeUY5aHc0NVhQZmI5VHlpbzZsOHZTS0F5RmU4T2lOalpkNE1Ra0ttYlJzTmxxR1Y5VlBoWF84UG1JSm5mcGVhb3E5YnZfU0k1blZHUl9zYUUzZE9sTEE2VWpaS0lsRVBNb0ZuRlZCMUFaUU9qQlhRRzZPTDg2eDZ2NHMwIiwKICAgICAgImt0eSI6ICJSU0EiLAogICAgICAicSI6ICJ2Q3pDQUlpdHV0MGx1V0djQloyLUFabURLc1RxNkkxcUp0RmlEYkIyZFBNQVlBNldOWTdaWEZoVWxsSjJrT2ZELWdlYjlkYkN2ODBxNEwyajVZSjZoOTBUc1NRWWVHRlljN1lZMGdCMU5VR3l5cXctb29QN0EtYlJmMGI3b3I4ajZJb0hzQTZKa2JranN6c3otbkJ2U2RmUURlZkRNSVc3Ni1ZWjN0c2hsY2MiLAogICAgICAiZCI6ICJtbFBOcm1zVVM5UmJtX1I5SElyeHdmeFYzZnJ2QzlaQktFZzRzc1ZZaThfY09lSjV2U1hyQV9laEtwa2g4QVhYaUdWUGpQbVlyd29xQzFVUksxUkZmLVg0dG10emV2OUVHaU12Z0JCaEF5RkdTSUd0VUNla2x4Q2dhb3BpMXdZSU1Bd0M0STZwMUtaZURxTVNCWVZGeHA5ZWlJZ2pwb05JbV9lR3hXUUs5VHNnYmk5T3lyc1VqaE9KLVczN2JVMEJWUU56UXpxODhCcGxmNzM3VmV1dy1FeDZaMk1iWXR3SWdfZ0JVb0JEZ0NrZkhoOVE4MElYcEZRV0x1RzgwenFrdkVwTHZ0RWxLbDRvQ3BHVnBjcmFUOFNsOGpYc3FDT1k0dnVRT19LRVUzS2VPNUNJbHd4eEhJYXZjQTE5cHFpSWJ5cm1LbThxS0ZEWHluUFJMSGFNZ1EiLAogICAgICAiZSI6ICJBUUFCIiwKICAgICAgImtpZCI6ICJyc2EzODRfMjA0OCIsCiAgICAgICJxaSI6ICJzMldTamVrVDl3S2JPbk9neGNoaDJPY3VubzE2Y20wS281Z3hoUWJTdVMyMldfUjJBR2ZVdkRieGF0cTRLakQ3THo3X1k2TjdTUkwzUVpudVhoZ1djeXgyNGhrUGppQUZLNmlkYVZKQzJqQmgycEZTUDVTNXZxZ0lsME12eWY4NjlwdkN4S0NzaGRKMGdlRWhveE93VkRPYXJqdTl2Zm9IQV90LWJoRlZrUnciLAogICAgICAiZHAiOiAiQlJhQTFqYVRydG9mTHZBSUJBYW1OSEVhSm51RU9zTVJJMFRCZXFuR1BNUm0tY2RjSG1OUVo5WUtqb2JpdXlmbnhGZ0piVDlSeElBRG0ySkpoZEp5RTN4Y1dTSzhmSjBSM1Jick1aT1dwako0QmJTVzFtU1VtRnlKTGxib3puRFhZR2RaZ1hzS0o1UkFrRUNQZFBCY3YwZVlkbk9NYWhfZndfaFZoNjRuZ2tFIiwKICAgICAgImFsZyI6ICJSU0EzODQiLAogICAgICAiZHEiOiAiUFJoVERKVlR3cDNXaDZfWFZrTjIwMUlpTWhxcElrUDN1UTYyUlRlTDNrQ2ZXSkNqMkZPLTRxcVRIQk0tQjZJWUVPLXpoVWZyQnhiMzJ1djNjS2JDWGFZN3BJSFJxQlFEQWQ2WGhHYzlwc0xqNThXd3VGY2RncERJYUFpRjNyc3NUMjJ4UFVvYkJFTVdBalV3bFJrNEtNTjItMnpLQk5FR3lIcDIzOUpKdnpVIiwKICAgICAgIm4iOiAidUpDWDVDbEZpM0JnTXBvOWhRSVZ2SDh0Vi1jLTVFdG5OeUZxVm91R3NlNWwyUG92MWJGb0tsRllsU25YTzNWUE9KRWR3azNDdl9VT0UtQzlqZERYRHpvS3Z4RURaTVM1TDZWMFpIVEJoNndIOV9iN3JHSlBxLV9RdlNkejczSzZxbHpGaUtQamRvdTF6VlFYTmZfblBZbnRnQkdNRUtBc1pRNGp0cWJCdE5lV0h0MF9UM001cEktTV9KNGVlRWpCTW95TkZuU2ExTEZDVmZRNl9YVnpjelp1TlRGMlh6UmdRWkFmcmJGRXZ6eXR1TzVMZTNTTXFrUUFJeDhFQmkwYXVlRUNqNEQ4cDNVNXFVRG92NEF2VnRJbUZlbFJvb1pBMHJtVW1KRHJ4WExrVkhuVUpzaUF6ZW9TLTNBSnV1bHJkMGpuNjJ5VjZHV2dFWklZMVNlZVd3IgogICAgfQogIF0KfQo