Skip to content

Commit 056ed4f

Browse files
committed
Merge pull request #46167 from l-trotta
* gh-46167: Polish "Add support for Elasticsearch API-key-based authentication" Add support for Elasticsearch API-key-based authentication Closes gh-46167
2 parents 79d409a + 86deef6 commit 056ed4f

File tree

4 files changed

+70
-0
lines changed

4 files changed

+70
-0
lines changed

module/spring-boot-elasticsearch/src/main/java/org/springframework/boot/elasticsearch/autoconfigure/ElasticsearchConnectionDetails.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ public interface ElasticsearchConnectionDetails extends ConnectionDetails {
5757
return null;
5858
}
5959

60+
/**
61+
* APIKey for authentication with Elasticsearch.
62+
* @return the API key for authentication with Elasticsearch or {@code null}
63+
*/
64+
default @Nullable String getApiKey() {
65+
return null;
66+
}
67+
6068
/**
6169
* Prefix added to the path of every request sent to Elasticsearch.
6270
* @return prefix added to the path of every request sent to Elasticsearch or

module/spring-boot-elasticsearch/src/main/java/org/springframework/boot/elasticsearch/autoconfigure/ElasticsearchProperties.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ public class ElasticsearchProperties {
4949
*/
5050
private @Nullable String password;
5151

52+
/**
53+
* API key for authentication with Elasticsearch.
54+
*/
55+
private @Nullable String apiKey;
56+
5257
/**
5358
* Connection timeout used when communicating with Elasticsearch.
5459
*/
@@ -95,6 +100,14 @@ public void setPassword(@Nullable String password) {
95100
this.password = password;
96101
}
97102

103+
public @Nullable String getApiKey() {
104+
return this.apiKey;
105+
}
106+
107+
public void setApiKey(@Nullable String apiKey) {
108+
this.apiKey = apiKey;
109+
}
110+
98111
public Duration getConnectionTimeout() {
99112
return this.connectionTimeout;
100113
}

module/spring-boot-elasticsearch/src/main/java/org/springframework/boot/elasticsearch/autoconfigure/ElasticsearchRestClientConfigurations.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@
3636
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
3737
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
3838
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
39+
import org.apache.hc.core5.http.Header;
3940
import org.apache.hc.core5.http.HttpHost;
41+
import org.apache.hc.core5.http.message.BasicHeader;
4042
import org.apache.hc.core5.reactor.IOReactorConfig;
4143
import org.apache.hc.core5.reactor.ssl.SSLBufferMode;
4244
import org.apache.hc.core5.util.Timeout;
@@ -67,6 +69,7 @@
6769
* @author Moritz Halbritter
6870
* @author Andy Wilkinson
6971
* @author Phillip Webb
72+
* @author Laura Trotta
7073
*/
7174
class ElasticsearchRestClientConfigurations {
7275

@@ -99,6 +102,10 @@ Rest5ClientBuilder elasticsearchRestClientBuilder(ElasticsearchConnectionDetails
99102
.stream()
100103
.map((node) -> new HttpHost(node.protocol().getScheme(), node.hostname(), node.port()))
101104
.toArray(HttpHost[]::new));
105+
if (connectionDetails.getApiKey() != null) {
106+
builder.setDefaultHeaders(
107+
new Header[] { new BasicHeader("Authorization", "ApiKey " + connectionDetails.getApiKey()), });
108+
}
102109
builder.setHttpClientConfigCallback((httpClientBuilder) -> builderCustomizers.orderedStream()
103110
.forEach((customizer) -> customizer.customize(httpClientBuilder)));
104111
builder.setConnectionManagerCallback((connectionManagerBuilder) -> builderCustomizers.orderedStream()
@@ -275,6 +282,11 @@ public List<Node> getNodes() {
275282
return this.properties.getPassword();
276283
}
277284

285+
@Override
286+
public @Nullable String getApiKey() {
287+
return this.properties.getApiKey();
288+
}
289+
278290
@Override
279291
public @Nullable String getPathPrefix() {
280292
return this.properties.getPathPrefix();

module/spring-boot-elasticsearch/src/test/java/org/springframework/boot/elasticsearch/autoconfigure/ElasticsearchRestClientAutoConfigurationTests.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
3434
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
3535
import org.apache.hc.core5.function.Resolver;
36+
import org.apache.hc.core5.http.Header;
3637
import org.apache.hc.core5.http.HttpHost;
3738
import org.apache.hc.core5.http.config.Registry;
3839
import org.apache.hc.core5.util.Timeout;
@@ -62,6 +63,7 @@
6263
* @author Andy Wilkinson
6364
* @author Moritz Halbritter
6465
* @author Phillip Webb
66+
* @author Laura Trotta
6567
*/
6668
class ElasticsearchRestClientAutoConfigurationTests {
6769

@@ -193,6 +195,41 @@ void configureUriWithUsernameAndPasswordWhenUsernameAndPasswordPropertiesSet() {
193195
});
194196
}
195197

198+
@Test
199+
void whenApiKeyIsConfiguredThenAuthorizationHeaderIsPresent() {
200+
this.contextRunner.withPropertyValues("spring.elasticsearch.api-key=some-api-key").run((context) -> {
201+
Rest5Client client = context.getBean(Rest5Client.class);
202+
assertThat(client).extracting("defaultHeaders", InstanceOfAssertFactories.list(Header.class))
203+
.satisfiesOnlyOnce((header) -> {
204+
assertThat(header.getName().equals("Authorization"));
205+
assertThat(header.getValue().equals("ApiKey some-api-key"));
206+
});
207+
});
208+
}
209+
210+
@Test
211+
void whenApiKeyAndUsernameAndPasswordAreConfiguredThenBothFormsOfCredentialsArePresent() {
212+
this.contextRunner
213+
.withPropertyValues("spring.elasticsearch.api-key=some-api-key", "spring.elasticsearch.username=alice",
214+
"spring.elasticsearch.password=secret")
215+
.run((context) -> {
216+
Rest5Client client = context.getBean(Rest5Client.class);
217+
assertThat(client).extracting("defaultHeaders", InstanceOfAssertFactories.list(Header.class))
218+
.satisfiesOnlyOnce((header) -> {
219+
assertThat(header.getName().equals("Authorization"));
220+
assertThat(header.getValue().equals("ApiKey some-api-key"));
221+
});
222+
assertThat(client)
223+
.extracting("client.credentialsProvider", InstanceOfAssertFactories.type(CredentialsProvider.class))
224+
.satisfies((credentialsProvider) -> {
225+
UsernamePasswordCredentials defaultCredentials = (UsernamePasswordCredentials) credentialsProvider
226+
.getCredentials(new AuthScope(null, -1), null);
227+
assertThat(defaultCredentials.getUserPrincipal().getName()).isEqualTo("alice");
228+
assertThat(defaultCredentials.getUserPassword()).containsExactly("secret".toCharArray());
229+
});
230+
});
231+
}
232+
196233
@Test
197234
void configureWithCustomPathPrefix() {
198235
this.contextRunner.withPropertyValues("spring.elasticsearch.path-prefix=/some/prefix").run((context) -> {

0 commit comments

Comments
 (0)