Skip to content

Commit 34ea976

Browse files
authored
Don't remove warning headers on all failure (#76434)
* Don't remove warning headers on all failure We shouldn't remove warning when request is failing not because of security reasons (syntax error for ex.). Note, that security related failure could happen not only during authentication (therefore we will check for the rest status), also all failures happened during authentication will be considered security related and warnings will be removed from the response. Resolves: #75739
1 parent 97f6b0c commit 34ea976

File tree

2 files changed

+170
-9
lines changed

2 files changed

+170
-9
lines changed

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

+27-9
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ public class SecurityRestFilter implements RestHandler {
4646
private final ThreadContext threadContext;
4747
private final boolean extractClientCertificate;
4848

49+
public enum ActionType {
50+
Authentication("Authentication"),
51+
SecondaryAuthentication("Secondary authentication"),
52+
RequestHandling("Request handling");
53+
54+
private final String name;
55+
ActionType(String name) { this.name = name; }
56+
@Override
57+
public String toString() { return name; }
58+
}
59+
4960
public SecurityRestFilter(Settings settings, ThreadContext threadContext, AuthenticationService authenticationService,
5061
SecondaryAuthenticator secondaryAuthenticator, RestHandler restHandler, boolean extractClientCertificate) {
5162
this.settings = settings;
@@ -89,16 +100,20 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c
89100
logger.trace("Found secondary authentication {} in REST request [{}]", secondaryAuthentication, requestUri);
90101
}
91102
RemoteHostHeader.process(request, threadContext);
92-
restHandler.handleRequest(request, channel, client);
103+
try {
104+
restHandler.handleRequest(request, channel, client);
105+
} catch (Exception e) {
106+
handleException(ActionType.RequestHandling, request, channel, e);
107+
}
93108
},
94-
e -> handleException("Secondary authentication", request, channel, e)));
95-
}, e -> handleException("Authentication", request, channel, e)));
109+
e -> handleException(ActionType.SecondaryAuthentication, request, channel, e)));
110+
}, e -> handleException(ActionType.Authentication, request, channel, e)));
96111
} else {
97112
restHandler.handleRequest(request, channel, client);
98113
}
99114
}
100115

101-
private void handleException(String actionType, RestRequest request, RestChannel channel, Exception e) {
116+
protected void handleException(ActionType actionType, RestRequest request, RestChannel channel, Exception e) {
102117
logger.debug(new ParameterizedMessage("{} failed for REST request [{}]", actionType, request.uri()), e);
103118
final RestStatus restStatus = ExceptionsHelper.status(e);
104119
try {
@@ -109,11 +124,14 @@ private void handleException(String actionType, RestRequest request, RestChannel
109124

110125
@Override
111126
public Map<String, List<String>> filterHeaders(Map<String, List<String>> headers) {
112-
if (headers.containsKey("Warning")) {
113-
headers = Maps.copyMapWithRemovedEntry(headers, "Warning");
114-
}
115-
if (headers.containsKey("X-elastic-product")) {
116-
headers = Maps.copyMapWithRemovedEntry(headers, "X-elastic-product");
127+
if (actionType != ActionType.RequestHandling
128+
|| (restStatus == RestStatus.UNAUTHORIZED || restStatus == RestStatus.FORBIDDEN)) {
129+
if (headers.containsKey("Warning")) {
130+
headers = Maps.copyMapWithRemovedEntry(headers, "Warning");
131+
}
132+
if (headers.containsKey("X-elastic-product")) {
133+
headers = Maps.copyMapWithRemovedEntry(headers, "X-elastic-product");
134+
}
117135
}
118136
return headers;
119137
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.security.rest;
9+
10+
import org.elasticsearch.ElasticsearchStatusException;
11+
import org.elasticsearch.action.ActionListener;
12+
import org.elasticsearch.common.collect.MapBuilder;
13+
import org.elasticsearch.common.settings.Settings;
14+
import org.elasticsearch.common.util.concurrent.ThreadContext;
15+
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
16+
import org.elasticsearch.common.xcontent.json.JsonXContent;
17+
import org.elasticsearch.rest.BytesRestResponse;
18+
import org.elasticsearch.rest.RestChannel;
19+
import org.elasticsearch.rest.RestHandler;
20+
import org.elasticsearch.rest.RestRequest;
21+
import org.elasticsearch.rest.RestResponse;
22+
import org.elasticsearch.rest.RestStatus;
23+
import org.elasticsearch.test.ESTestCase;
24+
import org.elasticsearch.test.rest.FakeRestRequest;
25+
import org.elasticsearch.xpack.core.security.authc.Authentication;
26+
import org.elasticsearch.xpack.security.authc.AuthenticationService;
27+
import org.elasticsearch.xpack.security.authc.support.SecondaryAuthenticator;
28+
import org.junit.Before;
29+
import org.mockito.ArgumentCaptor;
30+
31+
import java.util.Collections;
32+
import java.util.List;
33+
import java.util.Map;
34+
35+
import static org.elasticsearch.mock.orig.Mockito.doThrow;
36+
import static org.elasticsearch.test.ActionListenerUtils.anyActionListener;
37+
import static org.mockito.Matchers.eq;
38+
import static org.mockito.Mockito.doAnswer;
39+
import static org.mockito.Mockito.mock;
40+
import static org.mockito.Mockito.verify;
41+
import static org.mockito.Mockito.when;
42+
43+
public class SecurityRestFilterWarningHeadersTests extends ESTestCase {
44+
private ThreadContext threadContext;
45+
private AuthenticationService authcService;
46+
private SecondaryAuthenticator secondaryAuthenticator;
47+
private RestHandler restHandler;
48+
49+
@Override
50+
protected boolean enableWarningsCheck() {
51+
return false;
52+
}
53+
54+
@Before
55+
public void init() throws Exception {
56+
authcService = mock(AuthenticationService.class);
57+
restHandler = mock(RestHandler.class);
58+
threadContext = new ThreadContext(Settings.EMPTY);
59+
secondaryAuthenticator = new SecondaryAuthenticator(Settings.EMPTY, threadContext, authcService);
60+
}
61+
62+
public void testResponseHeadersOnFailure() throws Exception {
63+
MapBuilder<String, List<String>> headers = new MapBuilder<>();
64+
headers.put("Warning", Collections.singletonList("Some warning header"));
65+
headers.put("X-elastic-product", Collections.singletonList("Some product header"));
66+
Map<String, List<String>> afterHeaders;
67+
68+
// Remove all the headers on authentication failures
69+
afterHeaders = testProcessAuthenticationFailed(RestStatus.BAD_REQUEST, headers);
70+
assertEquals(afterHeaders.size(), 0);
71+
afterHeaders = testProcessAuthenticationFailed(RestStatus.INTERNAL_SERVER_ERROR, headers);
72+
assertEquals(afterHeaders.size(), 0);
73+
afterHeaders = testProcessAuthenticationFailed(RestStatus.UNAUTHORIZED, headers);
74+
assertEquals(afterHeaders.size(), 0);
75+
afterHeaders = testProcessAuthenticationFailed(RestStatus.FORBIDDEN, headers);
76+
assertEquals(afterHeaders.size(), 0);
77+
78+
// On rest handling failures only remove headers if rest status is UNAUTHORIZED or FORBIDDEN
79+
afterHeaders = testProcessRestHandlingFailed(RestStatus.BAD_REQUEST, headers);
80+
assertEquals(afterHeaders.size(), 2);
81+
afterHeaders = testProcessRestHandlingFailed(RestStatus.INTERNAL_SERVER_ERROR, headers);
82+
assertEquals(afterHeaders.size(), 2);
83+
afterHeaders = testProcessRestHandlingFailed(RestStatus.UNAUTHORIZED, headers);
84+
assertEquals(afterHeaders.size(), 0);
85+
afterHeaders = testProcessRestHandlingFailed(RestStatus.FORBIDDEN, headers);
86+
assertEquals(afterHeaders.size(), 0);
87+
}
88+
89+
private Map<String, List<String>> testProcessRestHandlingFailed(RestStatus restStatus, MapBuilder<String, List<String>> headers)
90+
throws Exception {
91+
RestChannel channel = mock(RestChannel.class);
92+
SecurityRestFilter filter = new SecurityRestFilter(Settings.EMPTY, threadContext, authcService, secondaryAuthenticator,
93+
restHandler, false);
94+
RestRequest request = new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY).build();
95+
Authentication primaryAuthentication = mock(Authentication.class);
96+
when(primaryAuthentication.encode()).thenReturn(randomAlphaOfLengthBetween(12, 36));
97+
doAnswer(i -> {
98+
final Object[] arguments = i.getArguments();
99+
@SuppressWarnings("unchecked")
100+
ActionListener<Authentication> callback = (ActionListener<Authentication>) arguments[arguments.length - 1];
101+
callback.onResponse(primaryAuthentication);
102+
return null;
103+
}).when(authcService).authenticate(eq(request), anyActionListener());
104+
Authentication secondaryAuthentication = mock(Authentication.class);
105+
when(secondaryAuthentication.encode()).thenReturn(randomAlphaOfLengthBetween(12, 36));
106+
doAnswer(i -> {
107+
final Object[] arguments = i.getArguments();
108+
@SuppressWarnings("unchecked")
109+
ActionListener<Authentication> callback = (ActionListener<Authentication>) arguments[arguments.length - 1];
110+
callback.onResponse(secondaryAuthentication);
111+
return null;
112+
}).when(authcService).authenticate(eq(request), eq(false), anyActionListener());
113+
doThrow(new ElasticsearchStatusException("Rest handling failed", restStatus, ""))
114+
.when(restHandler).handleRequest(request, channel, null);
115+
when(channel.request()).thenReturn(request);
116+
when(channel.newErrorBuilder()).thenReturn(JsonXContent.contentBuilder());
117+
filter.handleRequest(request, channel, null);
118+
ArgumentCaptor<BytesRestResponse> response = ArgumentCaptor.forClass(BytesRestResponse.class);
119+
verify(channel).sendResponse(response.capture());
120+
RestResponse restResponse = response.getValue();
121+
return restResponse.filterHeaders(headers.immutableMap());
122+
}
123+
124+
private Map<String, List<String>> testProcessAuthenticationFailed(RestStatus restStatus, MapBuilder<String, List<String>> headers)
125+
throws Exception {
126+
RestChannel channel = mock(RestChannel.class);
127+
SecurityRestFilter filter = new SecurityRestFilter(Settings.EMPTY, threadContext, authcService, secondaryAuthenticator,
128+
restHandler, false);
129+
RestRequest request = new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY).build();
130+
doAnswer((i) -> {
131+
ActionListener<?> callback = (ActionListener<?>) i.getArguments()[1];
132+
callback.onFailure(new ElasticsearchStatusException("Authentication failed", restStatus, ""));
133+
return Void.TYPE;
134+
}).when(authcService).authenticate(eq(request), anyActionListener());
135+
when(channel.request()).thenReturn(request);
136+
when(channel.newErrorBuilder()).thenReturn(JsonXContent.contentBuilder());
137+
filter.handleRequest(request, channel, null);
138+
ArgumentCaptor<BytesRestResponse> response = ArgumentCaptor.forClass(BytesRestResponse.class);
139+
verify(channel).sendResponse(response.capture());
140+
RestResponse restResponse = response.getValue();
141+
return restResponse.filterHeaders(headers.immutableMap());
142+
}
143+
}

0 commit comments

Comments
 (0)