Update authc failure headers on license change (#61734) (#62442)

Backport of #61734
This commit is contained in:
Lyudmila Fokina 2020-09-16 14:37:03 +02:00 committed by GitHub
parent 8d89a28126
commit 167172a057
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 35 deletions

View File

@ -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

View File

@ -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 + "]");
} }

View File

@ -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\""));
}
} }