Skip to content

Commit 167172a

Browse files
authored
Update authc failure headers on license change (#61734) (#62442)
Backport of #61734
1 parent 8d89a28 commit 167172a

File tree

3 files changed

+87
-35
lines changed

3 files changed

+87
-35
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/DefaultAuthenticationFailureHandler.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
* response headers like 'WWW-Authenticate'
2828
*/
2929
public class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandler {
30-
private final Map<String, List<String>> defaultFailureResponseHeaders;
30+
private volatile Map<String, List<String>> defaultFailureResponseHeaders;
3131

3232
/**
3333
* Constructs default authentication failure handler with provided default
@@ -55,6 +55,15 @@ public DefaultAuthenticationFailureHandler(final Map<String, List<String>> failu
5555
}
5656
}
5757

58+
/**
59+
* This method is called when failureResponseHeaders need to be set (at startup) or updated (if license state changes)
60+
*
61+
* @param failureResponseHeaders the Map of failure response headers to be set
62+
*/
63+
public void setHeaders(Map<String, List<String>> failureResponseHeaders){
64+
defaultFailureResponseHeaders = failureResponseHeaders;
65+
}
66+
5867
/**
5968
* For given 'WWW-Authenticate' header value returns the priority based on
6069
* the auth-scheme. Lower number denotes more secure and preferred

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java

+30-22
Original file line numberDiff line numberDiff line change
@@ -577,32 +577,40 @@ private AuthenticationFailureHandler createAuthenticationFailureHandler(final Re
577577
}
578578
if (failureHandler == null) {
579579
logger.debug("Using default authentication failure handler");
580-
final Map<String, List<String>> defaultFailureResponseHeaders = new HashMap<>();
581-
realms.asList().stream().forEach((realm) -> {
582-
Map<String, List<String>> realmFailureHeaders = realm.getAuthenticationFailureHeaders();
583-
realmFailureHeaders.entrySet().stream().forEach((e) -> {
584-
String key = e.getKey();
585-
e.getValue().stream()
586-
.filter(v -> defaultFailureResponseHeaders.computeIfAbsent(key, x -> new ArrayList<>()).contains(v) == false)
587-
.forEach(v -> defaultFailureResponseHeaders.get(key).add(v));
580+
Supplier<Map<String, List<String>>> headersSupplier = () -> {
581+
final Map<String, List<String>> defaultFailureResponseHeaders = new HashMap<>();
582+
realms.asList().stream().forEach((realm) -> {
583+
Map<String, List<String>> realmFailureHeaders = realm.getAuthenticationFailureHeaders();
584+
realmFailureHeaders.entrySet().stream().forEach((e) -> {
585+
String key = e.getKey();
586+
e.getValue().stream()
587+
.filter(v -> defaultFailureResponseHeaders.computeIfAbsent(key, x -> new ArrayList<>()).contains(v)
588+
== false)
589+
.forEach(v -> defaultFailureResponseHeaders.get(key).add(v));
590+
});
588591
});
589-
});
590592

591-
if (TokenService.isTokenServiceEnabled(settings)) {
592-
String bearerScheme = "Bearer realm=\"" + XPackField.SECURITY + "\"";
593-
if (defaultFailureResponseHeaders.computeIfAbsent("WWW-Authenticate", x -> new ArrayList<>())
594-
.contains(bearerScheme) == false) {
595-
defaultFailureResponseHeaders.get("WWW-Authenticate").add(bearerScheme);
593+
if (TokenService.isTokenServiceEnabled(settings)) {
594+
String bearerScheme = "Bearer realm=\"" + XPackField.SECURITY + "\"";
595+
if (defaultFailureResponseHeaders.computeIfAbsent("WWW-Authenticate", x -> new ArrayList<>())
596+
.contains(bearerScheme) == false) {
597+
defaultFailureResponseHeaders.get("WWW-Authenticate").add(bearerScheme);
598+
}
596599
}
597-
}
598-
if (API_KEY_SERVICE_ENABLED_SETTING.get(settings)) {
599-
final String apiKeyScheme = "ApiKey";
600-
if (defaultFailureResponseHeaders.computeIfAbsent("WWW-Authenticate", x -> new ArrayList<>())
601-
.contains(apiKeyScheme) == false) {
602-
defaultFailureResponseHeaders.get("WWW-Authenticate").add(apiKeyScheme);
600+
if (API_KEY_SERVICE_ENABLED_SETTING.get(settings)) {
601+
final String apiKeyScheme = "ApiKey";
602+
if (defaultFailureResponseHeaders.computeIfAbsent("WWW-Authenticate", x -> new ArrayList<>())
603+
.contains(apiKeyScheme) == false) {
604+
defaultFailureResponseHeaders.get("WWW-Authenticate").add(apiKeyScheme);
605+
}
603606
}
604-
}
605-
failureHandler = new DefaultAuthenticationFailureHandler(defaultFailureResponseHeaders);
607+
return defaultFailureResponseHeaders;
608+
};
609+
DefaultAuthenticationFailureHandler finalDefaultFailureHandler = new DefaultAuthenticationFailureHandler(headersSupplier.get());
610+
failureHandler = finalDefaultFailureHandler;
611+
getLicenseState().addListener(() -> {
612+
finalDefaultFailureHandler.setHeaders(headersSupplier.get());
613+
});
606614
} else {
607615
logger.debug("Using authentication failure handler from extension [" + extensionName + "]");
608616
}

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java

+47-12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
package org.elasticsearch.xpack.security;
77

8+
import org.elasticsearch.ElasticsearchSecurityException;
89
import org.elasticsearch.Version;
910
import org.elasticsearch.client.Client;
1011
import org.elasticsearch.cluster.ClusterName;
@@ -33,6 +34,7 @@
3334
import org.elasticsearch.test.VersionUtils;
3435
import org.elasticsearch.threadpool.ThreadPool;
3536
import org.elasticsearch.watcher.ResourceWatcherService;
37+
import org.elasticsearch.xpack.core.XPackField;
3638
import org.elasticsearch.xpack.core.XPackSettings;
3739
import org.elasticsearch.xpack.core.security.SecurityExtension;
3840
import org.elasticsearch.xpack.core.security.SecurityField;
@@ -72,6 +74,8 @@
7274
import static org.hamcrest.Matchers.empty;
7375
import static org.hamcrest.Matchers.equalTo;
7476
import static org.hamcrest.Matchers.hasItem;
77+
import static org.hamcrest.Matchers.instanceOf;
78+
import static org.hamcrest.Matchers.notNullValue;
7579
import static org.mockito.Mockito.mock;
7680
import static org.mockito.Mockito.when;
7781

@@ -94,18 +98,7 @@ public Map<String, Realm.Factory> getRealms(SecurityComponents components) {
9498
}
9599
}
96100

97-
private Collection<Object> createComponents(Settings testSettings, SecurityExtension... extensions) throws Exception {
98-
if (security != null) {
99-
throw new IllegalStateException("Security object already exists (" + security + ")");
100-
}
101-
Settings.Builder builder = Settings.builder()
102-
.put("xpack.security.enabled", true)
103-
.put(testSettings)
104-
.put("path.home", createTempDir());
105-
if (inFipsJvm()) {
106-
builder.put(XPackSettings.DIAGNOSE_TRUST_EXCEPTIONS_SETTING.getKey(), false);
107-
}
108-
Settings settings = builder.build();
101+
private Collection<Object> createComponentsUtil(Settings settings, SecurityExtension... extensions) throws Exception {
109102
Environment env = TestEnvironment.newEnvironment(settings);
110103
licenseState = new TestUtils.UpdatableLicenseState(settings);
111104
SSLService sslService = new SSLService(settings, env);
@@ -137,6 +130,36 @@ protected SSLService getSslService() {
137130
xContentRegistry(), env, new IndexNameExpressionResolver());
138131
}
139132

133+
private Collection<Object> createComponentsWithSecurityNotExplicitlyEnabled(Settings testSettings, SecurityExtension... extensions)
134+
throws Exception {
135+
if (security != null) {
136+
throw new IllegalStateException("Security object already exists (" + security + ")");
137+
}
138+
Settings.Builder builder = Settings.builder()
139+
.put(testSettings)
140+
.put("path.home", createTempDir());
141+
if (inFipsJvm()) {
142+
builder.put(XPackSettings.DIAGNOSE_TRUST_EXCEPTIONS_SETTING.getKey(), false);
143+
}
144+
Settings settings = builder.build();
145+
return createComponentsUtil(settings, extensions);
146+
}
147+
148+
private Collection<Object> createComponents(Settings testSettings, SecurityExtension... extensions) throws Exception {
149+
if (security != null) {
150+
throw new IllegalStateException("Security object already exists (" + security + ")");
151+
}
152+
Settings.Builder builder = Settings.builder()
153+
.put("xpack.security.enabled", true)
154+
.put(testSettings)
155+
.put("path.home", createTempDir());
156+
if (inFipsJvm()) {
157+
builder.put(XPackSettings.DIAGNOSE_TRUST_EXCEPTIONS_SETTING.getKey(), false);
158+
}
159+
Settings settings = builder.build();
160+
return createComponentsUtil(settings, extensions);
161+
}
162+
140163
private static <T> T findComponent(Class<T> type, Collection<Object> components) {
141164
for (Object obj : components) {
142165
if (type.isInstance(obj)) {
@@ -490,4 +513,16 @@ public void testValidateForFipsNoErrors() {
490513
Security.validateForFips(settings);
491514
// no exception thrown
492515
}
516+
517+
private void logAndFail(Exception e) {
518+
logger.error("unexpected exception", e);
519+
fail("unexpected exception " + e.getMessage());
520+
}
521+
522+
private void VerifyBasicAuthenticationHeader(Exception e) {
523+
assertThat(e, instanceOf(ElasticsearchSecurityException.class));
524+
assertThat(((ElasticsearchSecurityException) e).getHeader("WWW-Authenticate"), notNullValue());
525+
assertThat(((ElasticsearchSecurityException) e).getHeader("WWW-Authenticate"),
526+
hasItem("Basic realm=\"" + XPackField.SECURITY + "\" charset=\"UTF-8\""));
527+
}
493528
}

0 commit comments

Comments
 (0)