Browse Source

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
pull/47047/head
Andy Wilkinson 5 months ago
parent
commit
056ed4f226
  1. 8
      module/spring-boot-elasticsearch/src/main/java/org/springframework/boot/elasticsearch/autoconfigure/ElasticsearchConnectionDetails.java
  2. 13
      module/spring-boot-elasticsearch/src/main/java/org/springframework/boot/elasticsearch/autoconfigure/ElasticsearchProperties.java
  3. 12
      module/spring-boot-elasticsearch/src/main/java/org/springframework/boot/elasticsearch/autoconfigure/ElasticsearchRestClientConfigurations.java
  4. 37
      module/spring-boot-elasticsearch/src/test/java/org/springframework/boot/elasticsearch/autoconfigure/ElasticsearchRestClientAutoConfigurationTests.java

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

@ -57,6 +57,14 @@ public interface ElasticsearchConnectionDetails extends ConnectionDetails { @@ -57,6 +57,14 @@ public interface ElasticsearchConnectionDetails extends ConnectionDetails {
return null;
}
/**
* APIKey for authentication with Elasticsearch.
* @return the API key for authentication with Elasticsearch or {@code null}
*/
default @Nullable String getApiKey() {
return null;
}
/**
* Prefix added to the path of every request sent to Elasticsearch.
* @return prefix added to the path of every request sent to Elasticsearch or

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

@ -49,6 +49,11 @@ public class ElasticsearchProperties { @@ -49,6 +49,11 @@ public class ElasticsearchProperties {
*/
private @Nullable String password;
/**
* API key for authentication with Elasticsearch.
*/
private @Nullable String apiKey;
/**
* Connection timeout used when communicating with Elasticsearch.
*/
@ -95,6 +100,14 @@ public class ElasticsearchProperties { @@ -95,6 +100,14 @@ public class ElasticsearchProperties {
this.password = password;
}
public @Nullable String getApiKey() {
return this.apiKey;
}
public void setApiKey(@Nullable String apiKey) {
this.apiKey = apiKey;
}
public Duration getConnectionTimeout() {
return this.connectionTimeout;
}

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

@ -36,7 +36,9 @@ import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; @@ -36,7 +36,9 @@ import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.reactor.IOReactorConfig;
import org.apache.hc.core5.reactor.ssl.SSLBufferMode;
import org.apache.hc.core5.util.Timeout;
@ -67,6 +69,7 @@ import org.springframework.util.StringUtils; @@ -67,6 +69,7 @@ import org.springframework.util.StringUtils;
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @author Laura Trotta
*/
class ElasticsearchRestClientConfigurations {
@ -99,6 +102,10 @@ class ElasticsearchRestClientConfigurations { @@ -99,6 +102,10 @@ class ElasticsearchRestClientConfigurations {
.stream()
.map((node) -> new HttpHost(node.protocol().getScheme(), node.hostname(), node.port()))
.toArray(HttpHost[]::new));
if (connectionDetails.getApiKey() != null) {
builder.setDefaultHeaders(
new Header[] { new BasicHeader("Authorization", "ApiKey " + connectionDetails.getApiKey()), });
}
builder.setHttpClientConfigCallback((httpClientBuilder) -> builderCustomizers.orderedStream()
.forEach((customizer) -> customizer.customize(httpClientBuilder)));
builder.setConnectionManagerCallback((connectionManagerBuilder) -> builderCustomizers.orderedStream()
@ -275,6 +282,11 @@ class ElasticsearchRestClientConfigurations { @@ -275,6 +282,11 @@ class ElasticsearchRestClientConfigurations {
return this.properties.getPassword();
}
@Override
public @Nullable String getApiKey() {
return this.properties.getApiKey();
}
@Override
public @Nullable String getPathPrefix() {
return this.properties.getPathPrefix();

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

@ -33,6 +33,7 @@ import org.apache.hc.client5.http.config.RequestConfig; @@ -33,6 +33,7 @@ import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
import org.apache.hc.core5.function.Resolver;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.util.Timeout;
@ -62,6 +63,7 @@ import static org.mockito.Mockito.mock; @@ -62,6 +63,7 @@ import static org.mockito.Mockito.mock;
* @author Andy Wilkinson
* @author Moritz Halbritter
* @author Phillip Webb
* @author Laura Trotta
*/
class ElasticsearchRestClientAutoConfigurationTests {
@ -193,6 +195,41 @@ class ElasticsearchRestClientAutoConfigurationTests { @@ -193,6 +195,41 @@ class ElasticsearchRestClientAutoConfigurationTests {
});
}
@Test
void whenApiKeyIsConfiguredThenAuthorizationHeaderIsPresent() {
this.contextRunner.withPropertyValues("spring.elasticsearch.api-key=some-api-key").run((context) -> {
Rest5Client client = context.getBean(Rest5Client.class);
assertThat(client).extracting("defaultHeaders", InstanceOfAssertFactories.list(Header.class))
.satisfiesOnlyOnce((header) -> {
assertThat(header.getName().equals("Authorization"));
assertThat(header.getValue().equals("ApiKey some-api-key"));
});
});
}
@Test
void whenApiKeyAndUsernameAndPasswordAreConfiguredThenBothFormsOfCredentialsArePresent() {
this.contextRunner
.withPropertyValues("spring.elasticsearch.api-key=some-api-key", "spring.elasticsearch.username=alice",
"spring.elasticsearch.password=secret")
.run((context) -> {
Rest5Client client = context.getBean(Rest5Client.class);
assertThat(client).extracting("defaultHeaders", InstanceOfAssertFactories.list(Header.class))
.satisfiesOnlyOnce((header) -> {
assertThat(header.getName().equals("Authorization"));
assertThat(header.getValue().equals("ApiKey some-api-key"));
});
assertThat(client)
.extracting("client.credentialsProvider", InstanceOfAssertFactories.type(CredentialsProvider.class))
.satisfies((credentialsProvider) -> {
UsernamePasswordCredentials defaultCredentials = (UsernamePasswordCredentials) credentialsProvider
.getCredentials(new AuthScope(null, -1), null);
assertThat(defaultCredentials.getUserPrincipal().getName()).isEqualTo("alice");
assertThat(defaultCredentials.getUserPassword()).containsExactly("secret".toCharArray());
});
});
}
@Test
void configureWithCustomPathPrefix() {
this.contextRunner.withPropertyValues("spring.elasticsearch.path-prefix=/some/prefix").run((context) -> {

Loading…
Cancel
Save