Skip to content

Commit 484d3f4

Browse files
authored
Fixes CORS headers needed by Elastic clients (#85791)
* Fixes CORS headers needed by Elastic clients Updates the default value for the `http.cors.allow-headers` setting to include headers used by Elastic client libraries. Also adds the `access-control-expose-headers` header to responses to CORS requests so that clients can successfully perform their product check.
1 parent 354d3ae commit 484d3f4

File tree

5 files changed

+62
-4
lines changed

5 files changed

+62
-4
lines changed

docs/changelog/85791.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 85791
2+
summary: Fixes CORS headers needed by Elastic clients
3+
area: Infra/REST API
4+
type: bug
5+
issues: []

docs/reference/modules/http.asciidoc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,16 @@ Which methods to allow. Defaults to `OPTIONS, HEAD, GET, POST, PUT, DELETE`.
119119
// tag::http-cors-allow-headers-tag[]
120120
`http.cors.allow-headers` {ess-icon}::
121121
(<<static-cluster-setting,Static>>, string)
122-
Which headers to allow. Defaults to `X-Requested-With, Content-Type, Content-Length`.
122+
Which headers to allow. Defaults to `X-Requested-With, Content-Type, Content-Length, Authorization, Accept, User-Agent, X-Elastic-Client-Meta`.
123123
// end::http-cors-allow-headers-tag[]
124124

125+
[[http-cors-expose-headers]]
126+
// tag::http-cors-expose-headers-tag[]
127+
`http.cors.expose-headers` {ess-icon}::
128+
(<<static-cluster-setting,Static>>)
129+
Which response headers to expose in the client. Defaults to `X-elastic-product`.
130+
// end::http-cors-expose-headers-tag[]
131+
125132
[[http-cors-allow-credentials]]
126133
// tag::http-cors-allow-credentials-tag[]
127134
`http.cors.allow-credentials` {ess-icon}::

server/src/main/java/org/elasticsearch/http/CorsHandler.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_METHODS;
5555
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_ORIGIN;
5656
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ENABLED;
57+
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_EXPOSE_HEADERS;
5758
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_MAX_AGE;
5859

5960
/**
@@ -77,6 +78,7 @@ public class CorsHandler {
7778
public static final String ACCESS_CONTROL_ALLOW_METHODS = "access-control-allow-methods";
7879
public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "access-control-allow-origin";
7980
public static final String ACCESS_CONTROL_MAX_AGE = "access-control-max-age";
81+
public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "access-control-expose-headers";
8082

8183
private static final Pattern SCHEME_PATTERN = Pattern.compile("^https?://");
8284
private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss O", Locale.ENGLISH);
@@ -105,6 +107,7 @@ public void setCorsResponseHeaders(final HttpRequest httpRequest, final HttpResp
105107
}
106108
if (setOrigin(httpRequest, httpResponse)) {
107109
setAllowCredentials(httpResponse);
110+
setExposeHeaders(httpResponse);
108111
}
109112
}
110113

@@ -228,6 +231,12 @@ private void setAllowHeaders(final HttpResponse response) {
228231
}
229232
}
230233

234+
private void setExposeHeaders(final HttpResponse response) {
235+
for (String header : config.accessControlExposeHeaders) {
236+
response.addHeader(ACCESS_CONTROL_EXPOSE_HEADERS, header);
237+
}
238+
}
239+
231240
private void setAllowCredentials(final HttpResponse response) {
232241
if (config.isCredentialsAllowed()) {
233242
response.addHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
@@ -247,6 +256,7 @@ public static class Config {
247256
private final boolean credentialsAllowed;
248257
private final Set<RestRequest.Method> allowedRequestMethods;
249258
private final Set<String> allowedRequestHeaders;
259+
private final Set<String> accessControlExposeHeaders;
250260
private final long maxAge;
251261

252262
public Config(Builder builder) {
@@ -257,6 +267,7 @@ public Config(Builder builder) {
257267
this.credentialsAllowed = builder.allowCredentials;
258268
this.allowedRequestMethods = Collections.unmodifiableSet(builder.requestMethods);
259269
this.allowedRequestHeaders = Collections.unmodifiableSet(builder.requestHeaders);
270+
this.accessControlExposeHeaders = Collections.unmodifiableSet(builder.accessControlExposeHeaders);
260271
this.maxAge = builder.maxAge;
261272
}
262273

@@ -314,6 +325,8 @@ public String toString() {
314325
+ allowedRequestMethods
315326
+ ", allowedRequestHeaders="
316327
+ allowedRequestHeaders
328+
+ ", accessControlExposeHeaders="
329+
+ accessControlExposeHeaders
317330
+ ", maxAge="
318331
+ maxAge
319332
+ '}';
@@ -329,6 +342,7 @@ private static class Builder {
329342
long maxAge;
330343
private final Set<RestRequest.Method> requestMethods = new HashSet<>();
331344
private final Set<String> requestHeaders = new HashSet<>();
345+
private final Set<String> accessControlExposeHeaders = new HashSet<>();
332346

333347
private Builder() {
334348
anyOrigin = true;
@@ -380,6 +394,11 @@ public Builder allowedRequestHeaders(String[] headers) {
380394
return this;
381395
}
382396

397+
public Builder accessControlExposeHeaders(String[] headers) {
398+
accessControlExposeHeaders.addAll(Arrays.asList(headers));
399+
return this;
400+
}
401+
383402
public Config build() {
384403
return new Config(this);
385404
}
@@ -427,6 +446,7 @@ public static Config buildConfig(Settings settings) {
427446
Config config = builder.allowedRequestMethods(methods)
428447
.maxAge(SETTING_CORS_MAX_AGE.get(settings))
429448
.allowedRequestHeaders(Strings.tokenizeToStringArray(SETTING_CORS_ALLOW_HEADERS.get(settings), ","))
449+
.accessControlExposeHeaders(Strings.tokenizeToStringArray(SETTING_CORS_EXPOSE_HEADERS.get(settings), ","))
430450
.build();
431451
return config;
432452
}

server/src/main/java/org/elasticsearch/http/HttpTransportSettings.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,13 @@ public final class HttpTransportSettings {
4343
);
4444
public static final Setting<String> SETTING_CORS_ALLOW_HEADERS = new Setting<>(
4545
"http.cors.allow-headers",
46-
"X-Requested-With,Content-Type,Content-Length",
46+
"X-Requested-With,Content-Type,Content-Length,Authorization,Accept,User-Agent,X-Elastic-Client-Meta",
47+
(value) -> value,
48+
Property.NodeScope
49+
);
50+
public static final Setting<String> SETTING_CORS_EXPOSE_HEADERS = new Setting<>(
51+
"http.cors.expose-headers",
52+
"X-elastic-product",
4753
(value) -> value,
4854
Property.NodeScope
4955
);

server/src/test/java/org/elasticsearch/http/CorsHandlerTests.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,15 @@ public void testHandleInboundPreflightWithWildcardAllowCredentials() {
204204
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_METHODS), containsInAnyOrder("HEAD", "OPTIONS", "GET", "DELETE", "POST"));
205205
assertThat(
206206
headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_HEADERS),
207-
containsInAnyOrder("X-Requested-With", "Content-Type", "Content-Length")
207+
containsInAnyOrder(
208+
"X-Requested-With",
209+
"Content-Type",
210+
"Content-Length",
211+
"Authorization",
212+
"Accept",
213+
"User-Agent",
214+
"X-Elastic-Client-Meta"
215+
)
208216
);
209217
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_CREDENTIALS), containsInAnyOrder("true"));
210218
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_MAX_AGE), containsInAnyOrder("1728000"));
@@ -232,7 +240,15 @@ public void testHandleInboundPreflightWithValidOriginAllowCredentials() {
232240
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_METHODS), containsInAnyOrder("HEAD", "OPTIONS", "GET", "DELETE", "POST"));
233241
assertThat(
234242
headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_HEADERS),
235-
containsInAnyOrder("X-Requested-With", "Content-Type", "Content-Length")
243+
containsInAnyOrder(
244+
"X-Requested-With",
245+
"Content-Type",
246+
"Content-Length",
247+
"Authorization",
248+
"Accept",
249+
"User-Agent",
250+
"X-Elastic-Client-Meta"
251+
)
236252
);
237253
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_CREDENTIALS), containsInAnyOrder("true"));
238254
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_MAX_AGE), containsInAnyOrder("1728000"));
@@ -254,6 +270,7 @@ public void testSetResponseNonCorsRequest() {
254270

255271
Map<String, List<String>> headers = response.headers();
256272
assertNull(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_ORIGIN));
273+
assertNull(headers.get(CorsHandler.ACCESS_CONTROL_EXPOSE_HEADERS));
257274
}
258275

259276
public void testSetResponseHeadersWithWildcardOrigin() {
@@ -270,6 +287,7 @@ public void testSetResponseHeadersWithWildcardOrigin() {
270287

271288
Map<String, List<String>> headers = response.headers();
272289
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_ORIGIN), containsInAnyOrder("*"));
290+
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_EXPOSE_HEADERS), containsInAnyOrder("X-elastic-product"));
273291
assertNull(headers.get(CorsHandler.VARY));
274292
}
275293

@@ -288,6 +306,7 @@ public void testSetResponseHeadersWithCredentialsWithWildcard() {
288306

289307
Map<String, List<String>> headers = response.headers();
290308
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_ORIGIN), containsInAnyOrder("valid-origin"));
309+
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_EXPOSE_HEADERS), containsInAnyOrder("X-elastic-product"));
291310
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_CREDENTIALS), containsInAnyOrder("true"));
292311
assertThat(headers.get(CorsHandler.VARY), containsInAnyOrder(CorsHandler.ORIGIN));
293312
}
@@ -308,6 +327,7 @@ public void testSetResponseHeadersWithNonWildcardOrigin() {
308327

309328
Map<String, List<String>> headers = response.headers();
310329
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_ORIGIN), containsInAnyOrder("valid-origin"));
330+
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_EXPOSE_HEADERS), containsInAnyOrder("X-elastic-product"));
311331
assertThat(headers.get(CorsHandler.VARY), containsInAnyOrder(CorsHandler.ORIGIN));
312332
if (allowCredentials) {
313333
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_CREDENTIALS), containsInAnyOrder("true"));

0 commit comments

Comments
 (0)