mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-04-25 07:37:19 -04:00
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:
parent
354d3aea18
commit
484d3f4ada
5 changed files with 62 additions and 4 deletions
5
docs/changelog/85791.yaml
Normal file
5
docs/changelog/85791.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pr: 85791
|
||||||
|
summary: Fixes CORS headers needed by Elastic clients
|
||||||
|
area: Infra/REST API
|
||||||
|
type: bug
|
||||||
|
issues: []
|
|
@ -119,9 +119,16 @@ Which methods to allow. Defaults to `OPTIONS, HEAD, GET, POST, PUT, DELETE`.
|
||||||
// tag::http-cors-allow-headers-tag[]
|
// tag::http-cors-allow-headers-tag[]
|
||||||
`http.cors.allow-headers` {ess-icon}::
|
`http.cors.allow-headers` {ess-icon}::
|
||||||
(<<static-cluster-setting,Static>>, string)
|
(<<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[]
|
// 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]]
|
[[http-cors-allow-credentials]]
|
||||||
// tag::http-cors-allow-credentials-tag[]
|
// tag::http-cors-allow-credentials-tag[]
|
||||||
`http.cors.allow-credentials` {ess-icon}::
|
`http.cors.allow-credentials` {ess-icon}::
|
||||||
|
|
|
@ -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_METHODS;
|
||||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_ORIGIN;
|
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_ENABLED;
|
||||||
|
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_EXPOSE_HEADERS;
|
||||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_MAX_AGE;
|
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_METHODS = "access-control-allow-methods";
|
||||||
public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "access-control-allow-origin";
|
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_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 Pattern SCHEME_PATTERN = Pattern.compile("^https?://");
|
||||||
private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss O", Locale.ENGLISH);
|
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)) {
|
if (setOrigin(httpRequest, httpResponse)) {
|
||||||
setAllowCredentials(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) {
|
private void setAllowCredentials(final HttpResponse response) {
|
||||||
if (config.isCredentialsAllowed()) {
|
if (config.isCredentialsAllowed()) {
|
||||||
response.addHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
response.addHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
||||||
|
@ -247,6 +256,7 @@ public class CorsHandler {
|
||||||
private final boolean credentialsAllowed;
|
private final boolean credentialsAllowed;
|
||||||
private final Set<RestRequest.Method> allowedRequestMethods;
|
private final Set<RestRequest.Method> allowedRequestMethods;
|
||||||
private final Set<String> allowedRequestHeaders;
|
private final Set<String> allowedRequestHeaders;
|
||||||
|
private final Set<String> accessControlExposeHeaders;
|
||||||
private final long maxAge;
|
private final long maxAge;
|
||||||
|
|
||||||
public Config(Builder builder) {
|
public Config(Builder builder) {
|
||||||
|
@ -257,6 +267,7 @@ public class CorsHandler {
|
||||||
this.credentialsAllowed = builder.allowCredentials;
|
this.credentialsAllowed = builder.allowCredentials;
|
||||||
this.allowedRequestMethods = Collections.unmodifiableSet(builder.requestMethods);
|
this.allowedRequestMethods = Collections.unmodifiableSet(builder.requestMethods);
|
||||||
this.allowedRequestHeaders = Collections.unmodifiableSet(builder.requestHeaders);
|
this.allowedRequestHeaders = Collections.unmodifiableSet(builder.requestHeaders);
|
||||||
|
this.accessControlExposeHeaders = Collections.unmodifiableSet(builder.accessControlExposeHeaders);
|
||||||
this.maxAge = builder.maxAge;
|
this.maxAge = builder.maxAge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,6 +325,8 @@ public class CorsHandler {
|
||||||
+ allowedRequestMethods
|
+ allowedRequestMethods
|
||||||
+ ", allowedRequestHeaders="
|
+ ", allowedRequestHeaders="
|
||||||
+ allowedRequestHeaders
|
+ allowedRequestHeaders
|
||||||
|
+ ", accessControlExposeHeaders="
|
||||||
|
+ accessControlExposeHeaders
|
||||||
+ ", maxAge="
|
+ ", maxAge="
|
||||||
+ maxAge
|
+ maxAge
|
||||||
+ '}';
|
+ '}';
|
||||||
|
@ -329,6 +342,7 @@ public class CorsHandler {
|
||||||
long maxAge;
|
long maxAge;
|
||||||
private final Set<RestRequest.Method> requestMethods = new HashSet<>();
|
private final Set<RestRequest.Method> requestMethods = new HashSet<>();
|
||||||
private final Set<String> requestHeaders = new HashSet<>();
|
private final Set<String> requestHeaders = new HashSet<>();
|
||||||
|
private final Set<String> accessControlExposeHeaders = new HashSet<>();
|
||||||
|
|
||||||
private Builder() {
|
private Builder() {
|
||||||
anyOrigin = true;
|
anyOrigin = true;
|
||||||
|
@ -380,6 +394,11 @@ public class CorsHandler {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder accessControlExposeHeaders(String[] headers) {
|
||||||
|
accessControlExposeHeaders.addAll(Arrays.asList(headers));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Config build() {
|
public Config build() {
|
||||||
return new Config(this);
|
return new Config(this);
|
||||||
}
|
}
|
||||||
|
@ -427,6 +446,7 @@ public class CorsHandler {
|
||||||
Config config = builder.allowedRequestMethods(methods)
|
Config config = builder.allowedRequestMethods(methods)
|
||||||
.maxAge(SETTING_CORS_MAX_AGE.get(settings))
|
.maxAge(SETTING_CORS_MAX_AGE.get(settings))
|
||||||
.allowedRequestHeaders(Strings.tokenizeToStringArray(SETTING_CORS_ALLOW_HEADERS.get(settings), ","))
|
.allowedRequestHeaders(Strings.tokenizeToStringArray(SETTING_CORS_ALLOW_HEADERS.get(settings), ","))
|
||||||
|
.accessControlExposeHeaders(Strings.tokenizeToStringArray(SETTING_CORS_EXPOSE_HEADERS.get(settings), ","))
|
||||||
.build();
|
.build();
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,13 @@ public final class HttpTransportSettings {
|
||||||
);
|
);
|
||||||
public static final Setting<String> SETTING_CORS_ALLOW_HEADERS = new Setting<>(
|
public static final Setting<String> SETTING_CORS_ALLOW_HEADERS = new Setting<>(
|
||||||
"http.cors.allow-headers",
|
"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,
|
(value) -> value,
|
||||||
Property.NodeScope
|
Property.NodeScope
|
||||||
);
|
);
|
||||||
|
|
|
@ -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_METHODS), containsInAnyOrder("HEAD", "OPTIONS", "GET", "DELETE", "POST"));
|
||||||
assertThat(
|
assertThat(
|
||||||
headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_HEADERS),
|
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_ALLOW_CREDENTIALS), containsInAnyOrder("true"));
|
||||||
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_MAX_AGE), containsInAnyOrder("1728000"));
|
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_METHODS), containsInAnyOrder("HEAD", "OPTIONS", "GET", "DELETE", "POST"));
|
||||||
assertThat(
|
assertThat(
|
||||||
headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_HEADERS),
|
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_ALLOW_CREDENTIALS), containsInAnyOrder("true"));
|
||||||
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_MAX_AGE), containsInAnyOrder("1728000"));
|
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();
|
Map<String, List<String>> headers = response.headers();
|
||||||
assertNull(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_ORIGIN));
|
assertNull(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_ORIGIN));
|
||||||
|
assertNull(headers.get(CorsHandler.ACCESS_CONTROL_EXPOSE_HEADERS));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSetResponseHeadersWithWildcardOrigin() {
|
public void testSetResponseHeadersWithWildcardOrigin() {
|
||||||
|
@ -270,6 +287,7 @@ public class CorsHandlerTests extends ESTestCase {
|
||||||
|
|
||||||
Map<String, List<String>> headers = response.headers();
|
Map<String, List<String>> headers = response.headers();
|
||||||
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_ORIGIN), containsInAnyOrder("*"));
|
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));
|
assertNull(headers.get(CorsHandler.VARY));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,6 +306,7 @@ public class CorsHandlerTests extends ESTestCase {
|
||||||
|
|
||||||
Map<String, List<String>> headers = response.headers();
|
Map<String, List<String>> headers = response.headers();
|
||||||
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_ORIGIN), containsInAnyOrder("valid-origin"));
|
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.ACCESS_CONTROL_ALLOW_CREDENTIALS), containsInAnyOrder("true"));
|
||||||
assertThat(headers.get(CorsHandler.VARY), containsInAnyOrder(CorsHandler.ORIGIN));
|
assertThat(headers.get(CorsHandler.VARY), containsInAnyOrder(CorsHandler.ORIGIN));
|
||||||
}
|
}
|
||||||
|
@ -308,6 +327,7 @@ public class CorsHandlerTests extends ESTestCase {
|
||||||
|
|
||||||
Map<String, List<String>> headers = response.headers();
|
Map<String, List<String>> headers = response.headers();
|
||||||
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_ORIGIN), containsInAnyOrder("valid-origin"));
|
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));
|
assertThat(headers.get(CorsHandler.VARY), containsInAnyOrder(CorsHandler.ORIGIN));
|
||||||
if (allowCredentials) {
|
if (allowCredentials) {
|
||||||
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_CREDENTIALS), containsInAnyOrder("true"));
|
assertThat(headers.get(CorsHandler.ACCESS_CONTROL_ALLOW_CREDENTIALS), containsInAnyOrder("true"));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue