From 2a2c4ca1f05c8210a96b1d17b90e3c071f8360d3 Mon Sep 17 00:00:00 2001 From: Ankit Sethi Date: Mon, 20 Oct 2025 12:59:08 -0500 Subject: [PATCH 1/3] Configurable HTTP read and connect timeouts for url based SAML metadata resolution (#136058) * Fix + tests for #133542 * [CI] Auto commit changes from spotless * Update docs/changelog/136058.yaml * update summary --------- Co-authored-by: elasticsearchmachine (cherry picked from commit ddf9b68dee02e098780e1a1d326b6511f7d510f5) --- docs/changelog/136058.yaml | 5 + .../authc/saml/SamlRealmSettings.java | 17 +++ .../xpack/security/authc/saml/SamlRealm.java | 19 +++- .../security/authc/saml/SamlRealmTests.java | 106 ++++++++++++++++++ 4 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 docs/changelog/136058.yaml diff --git a/docs/changelog/136058.yaml b/docs/changelog/136058.yaml new file mode 100644 index 0000000000000..3ec09db8beabc --- /dev/null +++ b/docs/changelog/136058.yaml @@ -0,0 +1,5 @@ +pr: 136058 +summary: "Configurable HTTP read and connect timeouts for url based SAML metadata resolution" +area: Security +type: bug +issues: [] diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/saml/SamlRealmSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/saml/SamlRealmSettings.java index 831afcf44a1d0..eb19a2c9e7c43 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/saml/SamlRealmSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/saml/SamlRealmSettings.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import static org.elasticsearch.xpack.core.security.authc.support.SecuritySettingsUtil.verifyNonNullNotEmpty; @@ -63,6 +64,20 @@ public class SamlRealmSettings { key -> Setting.boolSetting(key, false, Setting.Property.NodeScope) ); + public static final Setting.AffixSetting IDP_METADATA_HTTP_CONNECT_TIMEOUT = Setting + .affixKeySetting( + RealmSettings.realmSettingPrefix(TYPE), + IDP_METADATA_SETTING_PREFIX + "http.connect_timeout", + key -> Setting.timeSetting(key, TimeValue.timeValueSeconds(5), Setting.Property.NodeScope) + ); + + public static final Setting.AffixSetting IDP_METADATA_HTTP_READ_TIMEOUT = Setting + .affixKeySetting( + RealmSettings.realmSettingPrefix(TYPE), + IDP_METADATA_SETTING_PREFIX + "http.read_timeout", + key -> Setting.timeSetting(key, TimeValue.timeValueSeconds(10), Setting.Property.NodeScope) + ); + public static final Setting.AffixSetting IDP_SINGLE_LOGOUT = Setting.affixKeySetting( RealmSettings.realmSettingPrefix(TYPE), "idp.use_single_logout", @@ -200,6 +215,8 @@ public static Set> getSettings() { IDP_METADATA_HTTP_REFRESH, IDP_METADATA_HTTP_MIN_REFRESH, IDP_METADATA_HTTP_FAIL_ON_ERROR, + IDP_METADATA_HTTP_CONNECT_TIMEOUT, + IDP_METADATA_HTTP_READ_TIMEOUT, IDP_SINGLE_LOGOUT, SP_ENTITY_ID, SP_ACS, diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlRealm.java index d82be264b2248..c60b4cbe2376e 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlRealm.java @@ -12,6 +12,7 @@ import net.shibboleth.utilities.java.support.xml.BasicParserPool; import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.logging.log4j.LogManager; @@ -120,8 +121,10 @@ import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.FORCE_AUTHN; import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.GROUPS_ATTRIBUTE; import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.IDP_ENTITY_ID; +import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.IDP_METADATA_HTTP_CONNECT_TIMEOUT; import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.IDP_METADATA_HTTP_FAIL_ON_ERROR; import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.IDP_METADATA_HTTP_MIN_REFRESH; +import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.IDP_METADATA_HTTP_READ_TIMEOUT; import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.IDP_METADATA_HTTP_REFRESH; import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.IDP_METADATA_PATH; import static org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings.IDP_SINGLE_LOGOUT; @@ -701,6 +704,14 @@ private static Tuple 0) { @@ -795,7 +806,13 @@ private static Tuple config = buildConfig("https://localhost:" + proxyServer.getPort(), builder -> { + builder.put( + SingleSpSamlRealmSettings.getFullSettingKey(REALM_NAME, SamlRealmSettings.IDP_METADATA_HTTP_CONNECT_TIMEOUT), + customConnectTimeout.getStringRep() + ); + builder.put( + SingleSpSamlRealmSettings.getFullSettingKey(REALM_NAME, SamlRealmSettings.IDP_METADATA_HTTP_READ_TIMEOUT), + customReadTimeout.getStringRep() + ); + }); + + // Verify settings are correctly configured + assertThat(config.v1().getSetting(SamlRealmSettings.IDP_METADATA_HTTP_CONNECT_TIMEOUT), equalTo(customConnectTimeout)); + assertThat(config.v1().getSetting(SamlRealmSettings.IDP_METADATA_HTTP_READ_TIMEOUT), equalTo(customReadTimeout)); + + final ResourceWatcherService watcherService = mock(ResourceWatcherService.class); + Tuple> tuple = SamlRealm.initializeResolver( + logger, + config.v1(), + config.v2(), + watcherService + ); + + try { + assertThat(proxyServer.requests().size(), greaterThanOrEqualTo(1)); + assertIdp1MetadataParsedCorrectly(tuple.v2().get()); + } finally { + tuple.v1().destroy(); + } + } + } + + public void testHttpMetadataWithDefaultTimeouts() throws Exception { + final Path path = getDataPath("idp1.xml"); + final String body = Files.readString(path); + TestsSSLService sslService = buildTestSslService(); + try (MockWebServer proxyServer = new MockWebServer(sslService.sslContext("xpack.security.http.ssl"), false)) { + proxyServer.start(); + proxyServer.enqueue(new MockResponse().setResponseCode(200).setBody(body).addHeader("Content-Type", "application/xml")); + + Tuple config = buildConfig("https://localhost:" + proxyServer.getPort()); + + // Verify default timeout values are used + assertThat(config.v1().getSetting(SamlRealmSettings.IDP_METADATA_HTTP_CONNECT_TIMEOUT), equalTo(TimeValue.timeValueSeconds(5))); + assertThat(config.v1().getSetting(SamlRealmSettings.IDP_METADATA_HTTP_READ_TIMEOUT), equalTo(TimeValue.timeValueSeconds(10))); + + final ResourceWatcherService watcherService = mock(ResourceWatcherService.class); + Tuple> tuple = SamlRealm.initializeResolver( + logger, + config.v1(), + config.v2(), + watcherService + ); + + try { + assertThat(proxyServer.requests().size(), greaterThanOrEqualTo(1)); + assertIdp1MetadataParsedCorrectly(tuple.v2().get()); + } finally { + tuple.v1().destroy(); + } + } + } + + public void testHttpMetadataConnectionTimeout() throws Exception { + // Use a non-routable IP address to simulate connection timeout + // 192.0.2.1 is reserved for documentation and will not be routable + final String unreachableUrl = "https://192.0.2.1:9999/metadata.xml"; + final TimeValue shortConnectTimeout = TimeValue.timeValueMillis(100); + + Tuple config = buildConfig(unreachableUrl, builder -> { + builder.put( + SingleSpSamlRealmSettings.getFullSettingKey(REALM_NAME, SamlRealmSettings.IDP_METADATA_HTTP_CONNECT_TIMEOUT), + shortConnectTimeout.getStringRep() + ); + builder.put(SingleSpSamlRealmSettings.getFullSettingKey(REALM_NAME, SamlRealmSettings.IDP_METADATA_HTTP_FAIL_ON_ERROR), false); + }); + + final ResourceWatcherService watcherService = mock(ResourceWatcherService.class); + + // initialization should complete even though the connection fails + Tuple> tuple = SamlRealm.initializeResolver( + logger, + config.v1(), + config.v2(), + watcherService + ); + + try { + EntityDescriptor descriptor = tuple.v2().get(); + assertThat(descriptor, instanceOf(UnresolvedEntity.class)); + } finally { + tuple.v1().destroy(); + } + } + private void assertIdp1MetadataParsedCorrectly(EntityDescriptor descriptor) { try { IDPSSODescriptor idpssoDescriptor = descriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS); From e1ab49c8d10f164c4c5b085c9ddc8650f69fb268 Mon Sep 17 00:00:00 2001 From: Ankit Sethi Date: Thu, 23 Oct 2025 13:59:11 -0500 Subject: [PATCH 2/3] spotless --- .../authc/saml/SamlRealmSettings.java | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/saml/SamlRealmSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/saml/SamlRealmSettings.java index eb19a2c9e7c43..ac9c01b219885 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/saml/SamlRealmSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/saml/SamlRealmSettings.java @@ -23,7 +23,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Function; import static org.elasticsearch.xpack.core.security.authc.support.SecuritySettingsUtil.verifyNonNullNotEmpty; @@ -64,19 +63,17 @@ public class SamlRealmSettings { key -> Setting.boolSetting(key, false, Setting.Property.NodeScope) ); - public static final Setting.AffixSetting IDP_METADATA_HTTP_CONNECT_TIMEOUT = Setting - .affixKeySetting( - RealmSettings.realmSettingPrefix(TYPE), - IDP_METADATA_SETTING_PREFIX + "http.connect_timeout", - key -> Setting.timeSetting(key, TimeValue.timeValueSeconds(5), Setting.Property.NodeScope) - ); + public static final Setting.AffixSetting IDP_METADATA_HTTP_CONNECT_TIMEOUT = Setting.affixKeySetting( + RealmSettings.realmSettingPrefix(TYPE), + IDP_METADATA_SETTING_PREFIX + "http.connect_timeout", + key -> Setting.timeSetting(key, TimeValue.timeValueSeconds(5), Setting.Property.NodeScope) + ); - public static final Setting.AffixSetting IDP_METADATA_HTTP_READ_TIMEOUT = Setting - .affixKeySetting( - RealmSettings.realmSettingPrefix(TYPE), - IDP_METADATA_SETTING_PREFIX + "http.read_timeout", - key -> Setting.timeSetting(key, TimeValue.timeValueSeconds(10), Setting.Property.NodeScope) - ); + public static final Setting.AffixSetting IDP_METADATA_HTTP_READ_TIMEOUT = Setting.affixKeySetting( + RealmSettings.realmSettingPrefix(TYPE), + IDP_METADATA_SETTING_PREFIX + "http.read_timeout", + key -> Setting.timeSetting(key, TimeValue.timeValueSeconds(10), Setting.Property.NodeScope) + ); public static final Setting.AffixSetting IDP_SINGLE_LOGOUT = Setting.affixKeySetting( RealmSettings.realmSettingPrefix(TYPE), From 69f56dd125caa9fdb0c11f0075f1017112cc0762 Mon Sep 17 00:00:00 2001 From: Ankit Sethi Date: Thu, 23 Oct 2025 14:02:16 -0500 Subject: [PATCH 3/3] fix test --- .../xpack/security/authc/saml/SamlRealmTests.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java index 8660f98319411..6a54839826c7c 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java @@ -1386,11 +1386,11 @@ public void testHttpMetadataWithCustomTimeouts() throws Exception { Tuple config = buildConfig("https://localhost:" + proxyServer.getPort(), builder -> { builder.put( - SingleSpSamlRealmSettings.getFullSettingKey(REALM_NAME, SamlRealmSettings.IDP_METADATA_HTTP_CONNECT_TIMEOUT), + getFullSettingKey(REALM_NAME, SamlRealmSettings.IDP_METADATA_HTTP_CONNECT_TIMEOUT), customConnectTimeout.getStringRep() ); builder.put( - SingleSpSamlRealmSettings.getFullSettingKey(REALM_NAME, SamlRealmSettings.IDP_METADATA_HTTP_READ_TIMEOUT), + getFullSettingKey(REALM_NAME, SamlRealmSettings.IDP_METADATA_HTTP_READ_TIMEOUT), customReadTimeout.getStringRep() ); }); @@ -1455,10 +1455,10 @@ public void testHttpMetadataConnectionTimeout() throws Exception { Tuple config = buildConfig(unreachableUrl, builder -> { builder.put( - SingleSpSamlRealmSettings.getFullSettingKey(REALM_NAME, SamlRealmSettings.IDP_METADATA_HTTP_CONNECT_TIMEOUT), + getFullSettingKey(REALM_NAME, SamlRealmSettings.IDP_METADATA_HTTP_CONNECT_TIMEOUT), shortConnectTimeout.getStringRep() ); - builder.put(SingleSpSamlRealmSettings.getFullSettingKey(REALM_NAME, SamlRealmSettings.IDP_METADATA_HTTP_FAIL_ON_ERROR), false); + builder.put(getFullSettingKey(REALM_NAME, SamlRealmSettings.IDP_METADATA_HTTP_FAIL_ON_ERROR), false); }); final ResourceWatcherService watcherService = mock(ResourceWatcherService.class);