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.
This commit is contained in:
Sylvain Wallez 2023-02-09 16:44:37 +01:00 committed by GitHub
parent 354d3aea18
commit 484d3f4ada
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 62 additions and 4 deletions

View file

@ -0,0 +1,5 @@
pr: 85791
summary: Fixes CORS headers needed by Elastic clients
area: Infra/REST API
type: bug
issues: []

View file

@ -119,9 +119,16 @@ Which methods to allow. Defaults to `OPTIONS, HEAD, GET, POST, PUT, DELETE`.
// tag::http-cors-allow-headers-tag[]
`http.cors.allow-headers` {ess-icon}::
(<<static-cluster-setting,Static>>, string)
Which headers to allow. Defaults to `X-Requested-With, Content-Type, Content-Length`.
Which headers to allow. Defaults to `X-Requested-With, Content-Type, Content-Length, Authorization, Accept, User-Agent, X-Elastic-Client-Meta`.
// end::http-cors-allow-headers-tag[]
[[http-cors-expose-headers]]
// tag::http-cors-expose-headers-tag[]
`http.cors.expose-headers` {ess-icon}::
(<<static-cluster-setting,Static>>)
Which response headers to expose in the client. Defaults to `X-elastic-product`.
// end::http-cors-expose-headers-tag[]
[[http-cors-allow-credentials]]
// tag::http-cors-allow-credentials-tag[]
`http.cors.allow-credentials` {ess-icon}::

View file

@ -54,6 +54,7 @@ import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_HE
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_METHODS;
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_ORIGIN;
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ENABLED;
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_EXPOSE_HEADERS;
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_MAX_AGE;
/**
@ -77,6 +78,7 @@ public class CorsHandler {
public static final String ACCESS_CONTROL_ALLOW_METHODS = "access-control-allow-methods";
public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "access-control-allow-origin";
public static final String ACCESS_CONTROL_MAX_AGE = "access-control-max-age";
public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "access-control-expose-headers";
private static final Pattern SCHEME_PATTERN = Pattern.compile("^https?://");
private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss O", Locale.ENGLISH);
@ -105,6 +107,7 @@ public class CorsHandler {
}
if (setOrigin(httpRequest, httpResponse)) {
setAllowCredentials(httpResponse);
setExposeHeaders(httpResponse);
}
}
@ -228,6 +231,12 @@ public class CorsHandler {
}
}
private void setExposeHeaders(final HttpResponse response) {
for (String header : config.accessControlExposeHeaders) {
response.addHeader(ACCESS_CONTROL_EXPOSE_HEADERS, header);
}
}
private void setAllowCredentials(final HttpResponse response) {
if (config.isCredentialsAllowed()) {
response.addHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
@ -247,6 +256,7 @@ public class CorsHandler {
private final boolean credentialsAllowed;
private final Set<RestRequest.Method> allowedRequestMethods;
private final Set<String> allowedRequestHeaders;
private final Set<String> accessControlExposeHeaders;
private final long maxAge;
public Config(Builder builder) {
@ -257,6 +267,7 @@ public class CorsHandler {
this.credentialsAllowed = builder.allowCredentials;
this.allowedRequestMethods = Collections.unmodifiableSet(builder.requestMethods);
this.allowedRequestHeaders = Collections.unmodifiableSet(builder.requestHeaders);
this.accessControlExposeHeaders = Collections.unmodifiableSet(builder.accessControlExposeHeaders);
this.maxAge = builder.maxAge;
}
@ -314,6 +325,8 @@ public class CorsHandler {
+ allowedRequestMethods
+ ", allowedRequestHeaders="
+ allowedRequestHeaders
+ ", accessControlExposeHeaders="
+ accessControlExposeHeaders
+ ", maxAge="
+ maxAge
+ '}';
@ -329,6 +342,7 @@ public class CorsHandler {
long maxAge;
private final Set<RestRequest.Method> requestMethods = new HashSet<>();
private final Set<String> requestHeaders = new HashSet<>();
private final Set<String> accessControlExposeHeaders = new HashSet<>();
private Builder() {
anyOrigin = true;
@ -380,6 +394,11 @@ public class CorsHandler {
return this;
}
public Builder accessControlExposeHeaders(String[] headers) {
accessControlExposeHeaders.addAll(Arrays.asList(headers));
return this;
}
public Config build() {
return new Config(this);
}
@ -427,6 +446,7 @@ public class CorsHandler {
Config config = builder.allowedRequestMethods(methods)
.maxAge(SETTING_CORS_MAX_AGE.get(settings))
.allowedRequestHeaders(Strings.tokenizeToStringArray(SETTING_CORS_ALLOW_HEADERS.get(settings), ","))
.accessControlExposeHeaders(Strings.tokenizeToStringArray(SETTING_CORS_EXPOSE_HEADERS.get(settings), ","))
.build();
return config;
}

View file

@ -43,7 +43,13 @@ public final class HttpTransportSettings {
);
public static final Setting<String> SETTING_CORS_ALLOW_HEADERS = new Setting<>(
"http.cors.allow-headers",
"X-Requested-With,Content-Type,Content-Length",
"X-Requested-With,Content-Type,Content-Length,Authorization,Accept,User-Agent,X-Elastic-Client-Meta",
(value) -> value,
Property.NodeScope
);
public static final Setting<String> SETTING_CORS_EXPOSE_HEADERS = new Setting<>(
"http.cors.expose-headers",
"X-elastic-product",
(value) -> value,
Property.NodeScope
);

View file

@ -204,7 +204,15 @@ public class CorsHandlerTests extends ESTestCase {
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_METHODS), containsInAnyOrder("HEAD", "OPTIONS", "GET", "DELETE", "POST"));
assertThat(
headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_HEADERS),
containsInAnyOrder("X-Requested-With", "Content-Type", "Content-Length")
containsInAnyOrder(
"X-Requested-With",
"Content-Type",
"Content-Length",
"Authorization",
"Accept",
"User-Agent",
"X-Elastic-Client-Meta"
)
);
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_CREDENTIALS), containsInAnyOrder("true"));
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_MAX_AGE), containsInAnyOrder("1728000"));
@ -232,7 +240,15 @@ public class CorsHandlerTests extends ESTestCase {
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_METHODS), containsInAnyOrder("HEAD", "OPTIONS", "GET", "DELETE", "POST"));
assertThat(
headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_HEADERS),
containsInAnyOrder("X-Requested-With", "Content-Type", "Content-Length")
containsInAnyOrder(
"X-Requested-With",
"Content-Type",
"Content-Length",
"Authorization",
"Accept",
"User-Agent",
"X-Elastic-Client-Meta"
)
);
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_CREDENTIALS), containsInAnyOrder("true"));
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_MAX_AGE), containsInAnyOrder("1728000"));
@ -254,6 +270,7 @@ public class CorsHandlerTests extends ESTestCase {
Map<String, List<String>> headers = response.headers();
assertNull(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_ORIGIN));
assertNull(headers.get(CorsHandler.ACCESS_CONTROL_EXPOSE_HEADERS));
}
public void testSetResponseHeadersWithWildcardOrigin() {
@ -270,6 +287,7 @@ public class CorsHandlerTests extends ESTestCase {
Map<String, List<String>> headers = response.headers();
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_ORIGIN), containsInAnyOrder("*"));
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_EXPOSE_HEADERS), containsInAnyOrder("X-elastic-product"));
assertNull(headers.get(CorsHandler.VARY));
}
@ -288,6 +306,7 @@ public class CorsHandlerTests extends ESTestCase {
Map<String, List<String>> headers = response.headers();
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_ORIGIN), containsInAnyOrder("valid-origin"));
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_EXPOSE_HEADERS), containsInAnyOrder("X-elastic-product"));
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_CREDENTIALS), containsInAnyOrder("true"));
assertThat(headers.get(CorsHandler.VARY), containsInAnyOrder(CorsHandler.ORIGIN));
}
@ -308,6 +327,7 @@ public class CorsHandlerTests extends ESTestCase {
Map<String, List<String>> headers = response.headers();
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_ORIGIN), containsInAnyOrder("valid-origin"));
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_EXPOSE_HEADERS), containsInAnyOrder("X-elastic-product"));
assertThat(headers.get(CorsHandler.VARY), containsInAnyOrder(CorsHandler.ORIGIN));
if (allowCredentials) {
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_CREDENTIALS), containsInAnyOrder("true"));