Skip to content

Commit d3894ca

Browse files
Add convenience API key param to remote reindex (#135949)
This adds a `source.remote.api_key` parameter to the reindex API. This is equivalent to using `source.remote.headers` with `Authorization` set to `ApiKey <api_key>`. It is a convenience for the user. Note on docs changes: The example request using an API key now uses an `applies-switch` to show both the pre-9.3 and post-9.3 / serverless versions. There are a few drive-by doc bug-fixes and other improvements to the docs, most notably: - Adding some subsections, as suggested by a tech writer. - Fixing the bug which meant that `<OTHER_HOST_URL>` was previously not being rendered, because `<` is a control character. - Repeating the note about using HTTPS for basic auth in the API key section, as we wouldn't recommend sending API keys over plain HTTP either. - A nit, but writing `<OTHER_HOST_URL>:9200` is strange, since we have a placeholder URL, and the port is part of the URL, so that's fixed. * Update docs/reference/elasticsearch/rest-apis/reindex-indices.md Co-authored-by: shainaraskas <[email protected]> * add cross-reference to SSL settings in docs * Remove the `When using {{escloud}}` caveat at the start of the API key section, since you can use API keys on pretty much any ES type --------- Co-authored-by: shainaraskas <[email protected]> ES-9691 #comment Added `api_key` to reindex API as convenience parameter in #135949
1 parent a1f4ac3 commit d3894ca

File tree

4 files changed

+124
-12
lines changed

4 files changed

+124
-12
lines changed

docs/changelog/135949.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 135949
2+
summary: Add convenience API key param to remote reindex
3+
area: Indices APIs
4+
type: enhancement
5+
issues: []

docs/reference/elasticsearch/rest-apis/reindex-indices.md

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ POST _reindex
597597
{
598598
"source": {
599599
"remote": {
600-
"host": "<OTHER_HOST_URL>:9200",
600+
"host": "&lt;OTHER_HOST_URL>",
601601
"username": "user",
602602
"password": "pass"
603603
},
@@ -619,20 +619,55 @@ POST _reindex
619619
% TEST[s/"username": "user",/"username": "test_admin",/]
620620
% TEST[s/"password": "pass"/"password": "x-pack-test-password"/]
621621

622-
The `host` parameter must contain a scheme, host, port (for example, `https://otherhost:9200`), and optional path (for example, `https://otherhost:9200/proxy`).
623-
The `username` and `password` parameters are optional, and when they are present the reindex API will connect to the remote {{es}} node using basic auth.
624-
Be sure to use `https` when using basic auth or the password will be sent in plain text. There are a range of settings available to configure the behaviour of the `https` connection.
622+
The `host` parameter must contain a scheme, host, port (for example, `https://<OTHER_HOST_URL>:9200`), and optional path (for example, `https://<OTHER_HOST_URL>:9200/proxy`).
625623

626-
When using {{ecloud}}, it is also possible to authenticate against the remote cluster through the use of a valid API key:
624+
### Using basic auth [reindex-basic-auth]
627625

626+
To authenticate with the remote cluster using basic auth, set the `username` and `password` parameters, as in the example above.
627+
Be sure to use `https` when using basic auth, or the password will be sent in plain text. There are a [range of settings](#reindex-ssl) available to configure the behaviour of the `https` connection.
628+
629+
### Using an API key [reindex-api-key]
630+
631+
It is also possible (and encouraged) to authenticate with the remote cluster through the use of a valid API key:
632+
633+
::::{applies-switch}
634+
635+
:::{applies-item} { "stack": "ga 9.3", "serverless": }
628636
```console
629637
POST _reindex
630638
{
631639
"source": {
632640
"remote": {
633-
"host": "<OTHER_HOST_URL>:9200",
641+
"host": "&lt;OTHER_HOST_URL>",
642+
"api_key": "&lt;API_KEY_VALUE>"
643+
},
644+
"index": "my-index-000001",
645+
"query": {
646+
"match": {
647+
"test": "data"
648+
}
649+
}
650+
},
651+
"dest": {
652+
"index": "my-new-index-000001"
653+
}
654+
}
655+
```
656+
% TEST[setup:host]
657+
% TEST[s/^/PUT my-index-000001\n/]
658+
% TEST[s/otherhost:9200",/\${host}",/]
659+
% TEST[s/"headers": \{[^}]*\}/"username": "test_admin", "password": "x-pack-test-password"/]
660+
:::
661+
662+
:::{applies-item} { "stack": "ga 9.0" }
663+
```console
664+
POST _reindex
665+
{
666+
"source": {
667+
"remote": {
668+
"host": "&lt;OTHER_HOST_URL>",
634669
"headers": {
635-
"Authorization": "ApiKey API_KEY_VALUE"
670+
"Authorization": "ApiKey &lt;API_KEY_VALUE>"
636671
}
637672
},
638673
"index": "my-index-000001",
@@ -651,15 +686,26 @@ POST _reindex
651686
% TEST[s/^/PUT my-index-000001\n/]
652687
% TEST[s/otherhost:9200",/\${host}",/]
653688
% TEST[s/"headers": \{[^}]*\}/"username": "test_admin", "password": "x-pack-test-password"/]
689+
:::
690+
691+
::::
692+
693+
694+
Be sure to use `https` when using an API key, or it will be sent in plain text. There are a [range of settings](#reindex-ssl) available to configure the behaviour of the `https` connection.
695+
696+
### Whitelisting remote hosts [reindex-remote-whitelist]
654697

655698
Remote hosts have to be explicitly allowed in `elasticsearch.yml` using the `reindex.remote.whitelist` property.
656-
It can be set to a comma delimited list of allowed remote `host` and `port` combinations.
699+
It can be set to a comma-delimited list of allowed remote `host` and `port` combinations.
657700
Scheme is ignored, only the host and port are used. For example:
658701

659702
```yaml
660703
reindex.remote.whitelist: [otherhost:9200, another:9200, 127.0.10.*:9200, localhost:*"]
661704
```
662-
The list of allowed hosts must be configured on any nodes that will coordinate the reindex.
705+
The list of allowed hosts must be configured on any node that will coordinate the reindex.
706+
707+
### Compatibility [reindex-remote-compatibility]
708+
663709
This feature should work with remote clusters of any version of {{es}} you are likely to find. This should allow you to upgrade from any version of {{es}} to the current version by reindexing from a cluster of the old version.
664710
::::{warning}
665711
{{es}} does not support forward compatibility across major versions. For example, you cannot reindex from a 7.x cluster into a 6.x cluster.
@@ -670,16 +716,18 @@ To enable queries sent to older versions of {{es}} the `query` parameter is sent
670716
Reindexing from remote clusters does not support manual or automatic slicing.
671717
::::
672718

719+
### Tuning parameters [reindex-remote-tuning]
720+
673721
Reindexing from a remote server uses an on-heap buffer that defaults to a maximum size of 100mb.
674-
If the remote index includes very large documents you'll need to use a smaller batch size.
722+
If the remote index includes very large documents you'll need to use a smaller batch size.
675723
The example below sets the batch size to `10` which is very, very small.
676724

677725
```console
678726
POST _reindex
679727
{
680728
"source": {
681729
"remote": {
682-
"host": "<OTHER_HOST_URL>:9200",
730+
"host": "&lt;OTHER_HOST_URL>",
683731
...
684732
},
685733
"index": "source",
@@ -709,7 +757,7 @@ POST _reindex
709757
{
710758
"source": {
711759
"remote": {
712-
"host": "<OTHER_HOST_URL>:9200",
760+
"host": "&lt;OTHER_HOST_URL>",
713761
...,
714762
"socket_timeout": "1m",
715763
"connect_timeout": "10s"

server/src/main/java/org/elasticsearch/index/reindex/ReindexRequest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.io.IOException;
3939
import java.net.URI;
4040
import java.net.URISyntaxException;
41+
import java.util.HashMap;
4142
import java.util.List;
4243
import java.util.Map;
4344
import java.util.function.Predicate;
@@ -417,6 +418,11 @@ static RemoteInfo buildRemoteInfo(Map<String, Object> source) throws IOException
417418
}
418419

419420
Map<String, String> headers = extractStringStringMap(remote, "headers");
421+
String apiKey = extractString(remote, "api_key");
422+
if (apiKey != null) {
423+
headers = headersWithApiKey(headers, apiKey);
424+
}
425+
420426
TimeValue socketTimeout = extractTimeValue(remote, "socket_timeout", RemoteInfo.DEFAULT_SOCKET_TIMEOUT);
421427
TimeValue connectTimeout = extractTimeValue(remote, "connect_timeout", RemoteInfo.DEFAULT_CONNECT_TIMEOUT);
422428
if (false == remote.isEmpty()) {
@@ -493,4 +499,18 @@ static void setMaxDocsValidateIdentical(AbstractBulkByScrollRequest<?> request,
493499
request.setMaxDocs(maxDocs);
494500
}
495501
}
502+
503+
/**
504+
* Returns a headers map with the {@code Authorization} key set to the value {@code "ApiKey <apiKey>"}. If the original map is a
505+
* {@link HashMap}, it is mutated; if not (e.g. it is {@link java.util.Collections#EMPTY_MAP}), it is copied. If the headers already
506+
* include an {@code Authorization} key, an {@link IllegalArgumentException} is thrown.
507+
*/
508+
private static Map<String, String> headersWithApiKey(Map<String, String> original, String apiKey) {
509+
if (original.keySet().stream().anyMatch(key -> key.equalsIgnoreCase("Authorization"))) {
510+
throw new IllegalArgumentException("Cannot specify both [api_key] and [headers] including [Authorization] key");
511+
}
512+
Map<String, String> updated = (original instanceof HashMap) ? original : new HashMap<>(original);
513+
updated.put("Authorization", "ApiKey " + apiKey);
514+
return updated;
515+
}
496516
}

server/src/test/java/org/elasticsearch/index/reindex/ReindexRequestTests.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,45 @@ public void testBuildRemoteInfoWithAllHostParts() throws IOException {
325325
assertEquals("[host] must be of the form [scheme]://[host]:[port](/[pathPrefix])? but was [https]", exception.getMessage());
326326
}
327327

328+
public void testBuildRemoteInfoWithApiKey() throws IOException {
329+
Map<String, Object> remote = new HashMap<>();
330+
remote.put("host", "https://example.com:9200");
331+
remote.put("api_key", "l3t-m3-1n");
332+
Map<String, Object> source = new HashMap<>();
333+
source.put("remote", remote);
334+
RemoteInfo remoteInfo = ReindexRequest.buildRemoteInfo(source);
335+
assertEquals(remoteInfo.getHeaders(), Map.of("Authorization", "ApiKey l3t-m3-1n"));
336+
}
337+
338+
public void testBuildRemoteInfoWithApiKeyAndOtherHeaders() throws IOException {
339+
Map<String, Object> originalHeaders = new HashMap<>();
340+
originalHeaders.put("X-Routing-Magic", "Abracadabra");
341+
originalHeaders.put("X-Tracing-Magic", "12345");
342+
Map<String, Object> remote = new HashMap<>();
343+
remote.put("host", "https://example.com:9200");
344+
remote.put("api_key", "l3t-m3-1n");
345+
remote.put("headers", originalHeaders);
346+
Map<String, Object> source = new HashMap<>();
347+
source.put("remote", remote);
348+
RemoteInfo remoteInfo = ReindexRequest.buildRemoteInfo(source);
349+
assertEquals(
350+
remoteInfo.getHeaders(),
351+
Map.of("X-Routing-Magic", "Abracadabra", "X-Tracing-Magic", "12345", "Authorization", "ApiKey l3t-m3-1n")
352+
);
353+
}
354+
355+
public void testBuildRemoteInfoWithConflictingApiKeyAndAuthorizationHeader() throws IOException {
356+
Map<String, Object> originalHeaders = new HashMap<>();
357+
originalHeaders.put("aUtHoRiZaTiOn", "op3n-s3s4m3"); // non-standard capitalization, but HTTP headers are not case-sensitive
358+
Map<String, Object> remote = new HashMap<>();
359+
remote.put("host", "https://example.com:9200");
360+
remote.put("api_key", "l3t-m3-1n");
361+
remote.put("headers", originalHeaders);
362+
Map<String, Object> source = new HashMap<>();
363+
source.put("remote", remote);
364+
assertThrows(IllegalArgumentException.class, () -> ReindexRequest.buildRemoteInfo(source));
365+
}
366+
328367
public void testReindexFromRemoteRequestParsing() throws IOException {
329368
BytesReference request;
330369
try (XContentBuilder b = JsonXContent.contentBuilder()) {

0 commit comments

Comments
 (0)