diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/DefaultAuthenticationFailureHandler.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/DefaultAuthenticationFailureHandler.java index d8954501b8b..afafe3e082c 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/DefaultAuthenticationFailureHandler.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/DefaultAuthenticationFailureHandler.java @@ -27,7 +27,7 @@ import static org.elasticsearch.xpack.core.security.support.Exceptions.authentic * response headers like 'WWW-Authenticate' */ public class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandler { - private final Map> defaultFailureResponseHeaders; + private volatile Map> defaultFailureResponseHeaders; /** * Constructs default authentication failure handler with provided default @@ -55,6 +55,15 @@ public class DefaultAuthenticationFailureHandler implements AuthenticationFailur } } + /** + * This method is called when failureResponseHeaders need to be set (at startup) or updated (if license state changes) + * + * @param failureResponseHeaders the Map of failure response headers to be set + */ + public void setHeaders(Map> failureResponseHeaders){ + defaultFailureResponseHeaders = failureResponseHeaders; + } + /** * For given 'WWW-Authenticate' header value returns the priority based on * the auth-scheme. Lower number denotes more secure and preferred diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 155685aa23e..f28bcf3cbf2 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -577,32 +577,40 @@ public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin, } if (failureHandler == null) { logger.debug("Using default authentication failure handler"); - final Map> defaultFailureResponseHeaders = new HashMap<>(); - realms.asList().stream().forEach((realm) -> { - Map> realmFailureHeaders = realm.getAuthenticationFailureHeaders(); - realmFailureHeaders.entrySet().stream().forEach((e) -> { - String key = e.getKey(); - e.getValue().stream() - .filter(v -> defaultFailureResponseHeaders.computeIfAbsent(key, x -> new ArrayList<>()).contains(v) == false) - .forEach(v -> defaultFailureResponseHeaders.get(key).add(v)); + Supplier>> headersSupplier = () -> { + final Map> defaultFailureResponseHeaders = new HashMap<>(); + realms.asList().stream().forEach((realm) -> { + Map> realmFailureHeaders = realm.getAuthenticationFailureHeaders(); + realmFailureHeaders.entrySet().stream().forEach((e) -> { + String key = e.getKey(); + e.getValue().stream() + .filter(v -> defaultFailureResponseHeaders.computeIfAbsent(key, x -> new ArrayList<>()).contains(v) + == false) + .forEach(v -> defaultFailureResponseHeaders.get(key).add(v)); + }); }); - }); - if (TokenService.isTokenServiceEnabled(settings)) { - String bearerScheme = "Bearer realm=\"" + XPackField.SECURITY + "\""; - if (defaultFailureResponseHeaders.computeIfAbsent("WWW-Authenticate", x -> new ArrayList<>()) - .contains(bearerScheme) == false) { - defaultFailureResponseHeaders.get("WWW-Authenticate").add(bearerScheme); + if (TokenService.isTokenServiceEnabled(settings)) { + String bearerScheme = "Bearer realm=\"" + XPackField.SECURITY + "\""; + if (defaultFailureResponseHeaders.computeIfAbsent("WWW-Authenticate", x -> new ArrayList<>()) + .contains(bearerScheme) == false) { + defaultFailureResponseHeaders.get("WWW-Authenticate").add(bearerScheme); + } } - } - if (API_KEY_SERVICE_ENABLED_SETTING.get(settings)) { - final String apiKeyScheme = "ApiKey"; - if (defaultFailureResponseHeaders.computeIfAbsent("WWW-Authenticate", x -> new ArrayList<>()) - .contains(apiKeyScheme) == false) { - defaultFailureResponseHeaders.get("WWW-Authenticate").add(apiKeyScheme); + if (API_KEY_SERVICE_ENABLED_SETTING.get(settings)) { + final String apiKeyScheme = "ApiKey"; + if (defaultFailureResponseHeaders.computeIfAbsent("WWW-Authenticate", x -> new ArrayList<>()) + .contains(apiKeyScheme) == false) { + defaultFailureResponseHeaders.get("WWW-Authenticate").add(apiKeyScheme); + } } - } - failureHandler = new DefaultAuthenticationFailureHandler(defaultFailureResponseHeaders); + return defaultFailureResponseHeaders; + }; + DefaultAuthenticationFailureHandler finalDefaultFailureHandler = new DefaultAuthenticationFailureHandler(headersSupplier.get()); + failureHandler = finalDefaultFailureHandler; + getLicenseState().addListener(() -> { + finalDefaultFailureHandler.setHeaders(headersSupplier.get()); + }); } else { logger.debug("Using authentication failure handler from extension [" + extensionName + "]"); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index 140d3e991e1..a097d00bfbd 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.security; +import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.Version; import org.elasticsearch.client.Client; import org.elasticsearch.cluster.ClusterName; @@ -33,6 +34,7 @@ import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.VersionUtils; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.watcher.ResourceWatcherService; +import org.elasticsearch.xpack.core.XPackField; import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.security.SecurityExtension; import org.elasticsearch.xpack.core.security.SecurityField; @@ -72,6 +74,8 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.notNullValue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -94,18 +98,7 @@ public class SecurityTests extends ESTestCase { } } - private Collection createComponents(Settings testSettings, SecurityExtension... extensions) throws Exception { - if (security != null) { - throw new IllegalStateException("Security object already exists (" + security + ")"); - } - Settings.Builder builder = Settings.builder() - .put("xpack.security.enabled", true) - .put(testSettings) - .put("path.home", createTempDir()); - if (inFipsJvm()) { - builder.put(XPackSettings.DIAGNOSE_TRUST_EXCEPTIONS_SETTING.getKey(), false); - } - Settings settings = builder.build(); + private Collection createComponentsUtil(Settings settings, SecurityExtension... extensions) throws Exception { Environment env = TestEnvironment.newEnvironment(settings); licenseState = new TestUtils.UpdatableLicenseState(settings); SSLService sslService = new SSLService(settings, env); @@ -137,6 +130,36 @@ public class SecurityTests extends ESTestCase { xContentRegistry(), env, new IndexNameExpressionResolver()); } + private Collection createComponentsWithSecurityNotExplicitlyEnabled(Settings testSettings, SecurityExtension... extensions) + throws Exception { + if (security != null) { + throw new IllegalStateException("Security object already exists (" + security + ")"); + } + Settings.Builder builder = Settings.builder() + .put(testSettings) + .put("path.home", createTempDir()); + if (inFipsJvm()) { + builder.put(XPackSettings.DIAGNOSE_TRUST_EXCEPTIONS_SETTING.getKey(), false); + } + Settings settings = builder.build(); + return createComponentsUtil(settings, extensions); + } + + private Collection createComponents(Settings testSettings, SecurityExtension... extensions) throws Exception { + if (security != null) { + throw new IllegalStateException("Security object already exists (" + security + ")"); + } + Settings.Builder builder = Settings.builder() + .put("xpack.security.enabled", true) + .put(testSettings) + .put("path.home", createTempDir()); + if (inFipsJvm()) { + builder.put(XPackSettings.DIAGNOSE_TRUST_EXCEPTIONS_SETTING.getKey(), false); + } + Settings settings = builder.build(); + return createComponentsUtil(settings, extensions); + } + private static T findComponent(Class type, Collection components) { for (Object obj : components) { if (type.isInstance(obj)) { @@ -490,4 +513,16 @@ public class SecurityTests extends ESTestCase { Security.validateForFips(settings); // no exception thrown } + + private void logAndFail(Exception e) { + logger.error("unexpected exception", e); + fail("unexpected exception " + e.getMessage()); + } + + private void VerifyBasicAuthenticationHeader(Exception e) { + assertThat(e, instanceOf(ElasticsearchSecurityException.class)); + assertThat(((ElasticsearchSecurityException) e).getHeader("WWW-Authenticate"), notNullValue()); + assertThat(((ElasticsearchSecurityException) e).getHeader("WWW-Authenticate"), + hasItem("Basic realm=\"" + XPackField.SECURITY + "\" charset=\"UTF-8\"")); + } }