Backport of #61734
This commit is contained in:
parent
8d89a28126
commit
167172a057
|
@ -27,7 +27,7 @@ import static org.elasticsearch.xpack.core.security.support.Exceptions.authentic
|
||||||
* response headers like 'WWW-Authenticate'
|
* response headers like 'WWW-Authenticate'
|
||||||
*/
|
*/
|
||||||
public class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
public class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||||||
private final Map<String, List<String>> defaultFailureResponseHeaders;
|
private volatile Map<String, List<String>> defaultFailureResponseHeaders;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs default authentication failure handler with provided default
|
* 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<String, List<String>> failureResponseHeaders){
|
||||||
|
defaultFailureResponseHeaders = failureResponseHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For given 'WWW-Authenticate' header value returns the priority based on
|
* For given 'WWW-Authenticate' header value returns the priority based on
|
||||||
* the auth-scheme. Lower number denotes more secure and preferred
|
* the auth-scheme. Lower number denotes more secure and preferred
|
||||||
|
|
|
@ -577,32 +577,40 @@ public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin,
|
||||||
}
|
}
|
||||||
if (failureHandler == null) {
|
if (failureHandler == null) {
|
||||||
logger.debug("Using default authentication failure handler");
|
logger.debug("Using default authentication failure handler");
|
||||||
final Map<String, List<String>> defaultFailureResponseHeaders = new HashMap<>();
|
Supplier<Map<String, List<String>>> headersSupplier = () -> {
|
||||||
realms.asList().stream().forEach((realm) -> {
|
final Map<String, List<String>> defaultFailureResponseHeaders = new HashMap<>();
|
||||||
Map<String, List<String>> realmFailureHeaders = realm.getAuthenticationFailureHeaders();
|
realms.asList().stream().forEach((realm) -> {
|
||||||
realmFailureHeaders.entrySet().stream().forEach((e) -> {
|
Map<String, List<String>> realmFailureHeaders = realm.getAuthenticationFailureHeaders();
|
||||||
String key = e.getKey();
|
realmFailureHeaders.entrySet().stream().forEach((e) -> {
|
||||||
e.getValue().stream()
|
String key = e.getKey();
|
||||||
.filter(v -> defaultFailureResponseHeaders.computeIfAbsent(key, x -> new ArrayList<>()).contains(v) == false)
|
e.getValue().stream()
|
||||||
.forEach(v -> defaultFailureResponseHeaders.get(key).add(v));
|
.filter(v -> defaultFailureResponseHeaders.computeIfAbsent(key, x -> new ArrayList<>()).contains(v)
|
||||||
|
== false)
|
||||||
|
.forEach(v -> defaultFailureResponseHeaders.get(key).add(v));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (TokenService.isTokenServiceEnabled(settings)) {
|
if (TokenService.isTokenServiceEnabled(settings)) {
|
||||||
String bearerScheme = "Bearer realm=\"" + XPackField.SECURITY + "\"";
|
String bearerScheme = "Bearer realm=\"" + XPackField.SECURITY + "\"";
|
||||||
if (defaultFailureResponseHeaders.computeIfAbsent("WWW-Authenticate", x -> new ArrayList<>())
|
if (defaultFailureResponseHeaders.computeIfAbsent("WWW-Authenticate", x -> new ArrayList<>())
|
||||||
.contains(bearerScheme) == false) {
|
.contains(bearerScheme) == false) {
|
||||||
defaultFailureResponseHeaders.get("WWW-Authenticate").add(bearerScheme);
|
defaultFailureResponseHeaders.get("WWW-Authenticate").add(bearerScheme);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if (API_KEY_SERVICE_ENABLED_SETTING.get(settings)) {
|
||||||
if (API_KEY_SERVICE_ENABLED_SETTING.get(settings)) {
|
final String apiKeyScheme = "ApiKey";
|
||||||
final String apiKeyScheme = "ApiKey";
|
if (defaultFailureResponseHeaders.computeIfAbsent("WWW-Authenticate", x -> new ArrayList<>())
|
||||||
if (defaultFailureResponseHeaders.computeIfAbsent("WWW-Authenticate", x -> new ArrayList<>())
|
.contains(apiKeyScheme) == false) {
|
||||||
.contains(apiKeyScheme) == false) {
|
defaultFailureResponseHeaders.get("WWW-Authenticate").add(apiKeyScheme);
|
||||||
defaultFailureResponseHeaders.get("WWW-Authenticate").add(apiKeyScheme);
|
}
|
||||||
}
|
}
|
||||||
}
|
return defaultFailureResponseHeaders;
|
||||||
failureHandler = new DefaultAuthenticationFailureHandler(defaultFailureResponseHeaders);
|
};
|
||||||
|
DefaultAuthenticationFailureHandler finalDefaultFailureHandler = new DefaultAuthenticationFailureHandler(headersSupplier.get());
|
||||||
|
failureHandler = finalDefaultFailureHandler;
|
||||||
|
getLicenseState().addListener(() -> {
|
||||||
|
finalDefaultFailureHandler.setHeaders(headersSupplier.get());
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Using authentication failure handler from extension [" + extensionName + "]");
|
logger.debug("Using authentication failure handler from extension [" + extensionName + "]");
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.security;
|
package org.elasticsearch.xpack.security;
|
||||||
|
|
||||||
|
import org.elasticsearch.ElasticsearchSecurityException;
|
||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.client.Client;
|
import org.elasticsearch.client.Client;
|
||||||
import org.elasticsearch.cluster.ClusterName;
|
import org.elasticsearch.cluster.ClusterName;
|
||||||
|
@ -33,6 +34,7 @@ import org.elasticsearch.test.ESTestCase;
|
||||||
import org.elasticsearch.test.VersionUtils;
|
import org.elasticsearch.test.VersionUtils;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||||
|
import org.elasticsearch.xpack.core.XPackField;
|
||||||
import org.elasticsearch.xpack.core.XPackSettings;
|
import org.elasticsearch.xpack.core.XPackSettings;
|
||||||
import org.elasticsearch.xpack.core.security.SecurityExtension;
|
import org.elasticsearch.xpack.core.security.SecurityExtension;
|
||||||
import org.elasticsearch.xpack.core.security.SecurityField;
|
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.empty;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.hasItem;
|
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.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ -94,18 +98,7 @@ public class SecurityTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<Object> createComponents(Settings testSettings, SecurityExtension... extensions) throws Exception {
|
private Collection<Object> createComponentsUtil(Settings settings, 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();
|
|
||||||
Environment env = TestEnvironment.newEnvironment(settings);
|
Environment env = TestEnvironment.newEnvironment(settings);
|
||||||
licenseState = new TestUtils.UpdatableLicenseState(settings);
|
licenseState = new TestUtils.UpdatableLicenseState(settings);
|
||||||
SSLService sslService = new SSLService(settings, env);
|
SSLService sslService = new SSLService(settings, env);
|
||||||
|
@ -137,6 +130,36 @@ public class SecurityTests extends ESTestCase {
|
||||||
xContentRegistry(), env, new IndexNameExpressionResolver());
|
xContentRegistry(), env, new IndexNameExpressionResolver());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Collection<Object> 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<Object> 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> T findComponent(Class<T> type, Collection<Object> components) {
|
private static <T> T findComponent(Class<T> type, Collection<Object> components) {
|
||||||
for (Object obj : components) {
|
for (Object obj : components) {
|
||||||
if (type.isInstance(obj)) {
|
if (type.isInstance(obj)) {
|
||||||
|
@ -490,4 +513,16 @@ public class SecurityTests extends ESTestCase {
|
||||||
Security.validateForFips(settings);
|
Security.validateForFips(settings);
|
||||||
// no exception thrown
|
// 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\""));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue