This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking.git
The following commit(s) were added to refs/heads/master by this push:
new 5328e3c8c7 feat: support opensearch client cert auth (#13641)
5328e3c8c7 is described below
commit 5328e3c8c7bdc6d3e1c2d727ccba9559b6738c2e
Author: kezhenxu94 <[email protected]>
AuthorDate: Mon Jan 5 16:15:51 2026 +0800
feat: support opensearch client cert auth (#13641)
---
.github/workflows/skywalking.yaml | 5 +-
.licenserc.yaml | 6 +-
dist-material/release-docs/LICENSE | 24 +-
docs/en/changes/changes.md | 1 +
docs/en/setup/backend/storages/elasticsearch.md | 43 ++-
oap-server-bom/pom.xml | 2 +-
.../client/elasticsearch/ElasticSearchClient.java | 21 +-
.../elasticsearch/bulk/ElasticSearchIT.java | 12 +-
.../elasticsearch/ElasticSearchBuilder.java | 398 ++++++++++++++-------
.../library/elasticsearch/ElasticSearchIT.java | 21 ++
.../src/main/resources/application.yml | 6 +-
.../StorageModuleElasticsearchConfig.java | 13 +
.../StorageModuleElasticsearchProvider.java | 41 ++-
.../cases/storage/opensearch/certs/admin-key.pem | 28 ++
.../cases/storage/opensearch/certs/admin.pem | 20 ++
.../cases/storage/opensearch/certs/client-key.pem | 28 ++
.../cases/storage/opensearch/certs/client.p12 | Bin 0 -> 2712 bytes
.../cases/storage/opensearch/certs/client.pem | 20 ++
.../cases/storage/opensearch/certs/node-key.pem | 28 ++
.../e2e-v2/cases/storage/opensearch/certs/node.pem | 23 ++
.../cases/storage/opensearch/certs/root-ca-key.pem | 28 ++
.../cases/storage/opensearch/certs/root-ca.pem | 22 ++
.../cases/storage/opensearch/certs/truststore.jks | Bin 0 -> 1334 bytes
.../cases/storage/opensearch/clientcert_config.yml | 48 +++
.../cases/storage/opensearch/docker-compose.yml | 95 ++++-
.../cases/storage/opensearch/generate-certs.sh | 88 +++++
.../cases/storage/opensearch/internal_users.yml | 25 ++
.../e2e-v2/cases/storage/opensearch/opensearch.yml | 36 ++
test/e2e-v2/java-test-service/pom.xml | 4 +-
29 files changed, 896 insertions(+), 190 deletions(-)
diff --git a/.github/workflows/skywalking.yaml
b/.github/workflows/skywalking.yaml
index 9e30c71b77..c0e3b12aa9 100644
--- a/.github/workflows/skywalking.yaml
+++ b/.github/workflows/skywalking.yaml
@@ -387,9 +387,6 @@ jobs:
- name: Storage ES 8.9.0
config: test/e2e-v2/cases/storage/es/e2e.yaml
env: ES_VERSION=8.18.1
- - name: Storage OpenSearch 1.1.0
- config: test/e2e-v2/cases/storage/opensearch/e2e.yaml
- env: OPENSEARCH_VERSION=1.1.0
- name: Storage OpenSearch 1.3.10
config: test/e2e-v2/cases/storage/opensearch/e2e.yaml
env: OPENSEARCH_VERSION=1.3.10
@@ -1121,4 +1118,4 @@ jobs:
[[ ${e2eJavaVersionResults} == 'success' ]] || [[ ${execute} !=
'true' && ${e2eJavaVersionResults} == 'skipped' ]] || exit -7;
[[ ${timeConsumingITResults} == 'success' ]] || [[ ${execute} !=
'true' && ${timeConsumingITResults} == 'skipped' ]] || exit -8;
- exit 0;
\ No newline at end of file
+ exit 0;
diff --git a/.licenserc.yaml b/.licenserc.yaml
index b77385e3fb..15b5adf114 100644
--- a/.licenserc.yaml
+++ b/.licenserc.yaml
@@ -109,10 +109,10 @@ dependency:
version: 2.13.4
license: Apache-2.0
- name: com.fasterxml.jackson.datatype:jackson-datatype-jsr310
- version: 2.18.2
+ version: 2.20.1
license: Apache-2.0
- name: com.fasterxml.jackson.datatype:jackson-datatype-jdk8
- version: 2.18.2
+ version: 2.20.1
license: Apache-2.0
- name: com.fasterxml.jackson.dataformat:jackson-dataformat-yaml
version: 2.15.2
@@ -139,7 +139,7 @@ dependency:
version: 1.2.1
license: Apache-2.0
- name: com.aayushatharva.brotli4j:service
- version: 1.18.0
+ version: 1.20.0
license: Apache-2.0
- name: io.vertx:vertx-grpc
version: 4.5.9
diff --git a/dist-material/release-docs/LICENSE
b/dist-material/release-docs/LICENSE
index e0ffeb6cec..6632dd2ac1 100644
--- a/dist-material/release-docs/LICENSE
+++ b/dist-material/release-docs/LICENSE
@@ -210,8 +210,8 @@ The following components are provided under the Apache-2.0
License. See project
The text of each license is the standard Apache 2.0 license.
https://mvnrepository.com/artifact/build.buf.protoc-gen-validate/pgv-java-stub/1.2.1
Apache-2.0
https://mvnrepository.com/artifact/build.buf.protoc-gen-validate/protoc-gen-validate/1.2.1
Apache-2.0
-
https://mvnrepository.com/artifact/com.aayushatharva.brotli4j/brotli4j/1.18.0
Apache-2.0
-
https://mvnrepository.com/artifact/com.aayushatharva.brotli4j/service/1.18.0
Apache-2.0
+
https://mvnrepository.com/artifact/com.aayushatharva.brotli4j/brotli4j/1.20.0
Apache-2.0
+
https://mvnrepository.com/artifact/com.aayushatharva.brotli4j/service/1.20.0
Apache-2.0
https://mvnrepository.com/artifact/com.alibaba.nacos/nacos-auth-plugin/2.3.2
Apache-2.0
https://mvnrepository.com/artifact/com.alibaba.nacos/nacos-client/2.3.2
Apache-2.0
https://mvnrepository.com/artifact/com.alibaba.nacos/nacos-encryption-plugin/2.3.2
Apache-2.0
@@ -222,8 +222,8 @@ The text of each license is the standard Apache 2.0 license.
https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind/2.16.0
Apache-2.0
https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.15.2
Apache-2.0
https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-guava/2.12.0
Apache-2.0
-
https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/2.18.2
Apache-2.0
-
https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.18.2
Apache-2.0
+
https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/2.20.1
Apache-2.0
+
https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.20.1
Apache-2.0
https://mvnrepository.com/artifact/com.fasterxml.jackson.module/jackson-module-kotlin/2.13.4
Apache-2.0
https://mvnrepository.com/artifact/com.fasterxml/classmate/1.5.1 Apache-2.0
https://mvnrepository.com/artifact/com.google.api.grpc/proto-google-common-protos/2.48.0
Apache-2.0
@@ -238,12 +238,12 @@ The text of each license is the standard Apache 2.0
license.
https://mvnrepository.com/artifact/com.google.inject/guice/4.1.0 Apache-2.0
https://mvnrepository.com/artifact/com.google.j2objc/j2objc-annotations/2.8
Apache-2.0
https://mvnrepository.com/artifact/com.graphql-java/java-dataloader/3.2.1
Apache-2.0
- https://mvnrepository.com/artifact/com.linecorp.armeria/armeria/1.32.0
Apache-2.0
-
https://mvnrepository.com/artifact/com.linecorp.armeria/armeria-graphql/1.32.0
Apache-2.0
-
https://mvnrepository.com/artifact/com.linecorp.armeria/armeria-graphql-protocol/1.32.0
Apache-2.0
-
https://mvnrepository.com/artifact/com.linecorp.armeria/armeria-grpc/1.32.0
Apache-2.0
-
https://mvnrepository.com/artifact/com.linecorp.armeria/armeria-grpc-protocol/1.32.0
Apache-2.0
-
https://mvnrepository.com/artifact/com.linecorp.armeria/armeria-protobuf/1.32.0
Apache-2.0
+ https://mvnrepository.com/artifact/com.linecorp.armeria/armeria/1.34.2
Apache-2.0
+
https://mvnrepository.com/artifact/com.linecorp.armeria/armeria-graphql/1.34.2
Apache-2.0
+
https://mvnrepository.com/artifact/com.linecorp.armeria/armeria-graphql-protocol/1.34.2
Apache-2.0
+
https://mvnrepository.com/artifact/com.linecorp.armeria/armeria-grpc/1.34.2
Apache-2.0
+
https://mvnrepository.com/artifact/com.linecorp.armeria/armeria-grpc-protocol/1.34.2
Apache-2.0
+
https://mvnrepository.com/artifact/com.linecorp.armeria/armeria-protobuf/1.34.2
Apache-2.0
https://mvnrepository.com/artifact/com.orbitz.consul/consul-client/1.5.3
Apache-2.0
https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp/3.14.9
Apache-2.0
https://mvnrepository.com/artifact/com.squareup.okio/okio/1.17.2 Apache-2.0
@@ -300,6 +300,7 @@ The text of each license is the standard Apache 2.0 license.
https://mvnrepository.com/artifact/io.grpc/grpc-services/1.70.0 Apache-2.0
https://mvnrepository.com/artifact/io.grpc/grpc-stub/1.70.0 Apache-2.0
https://mvnrepository.com/artifact/io.grpc/grpc-util/1.70.0 Apache-2.0
+ https://mvnrepository.com/artifact/io.micrometer/context-propagation/1.2.0
Apache-2.0
https://mvnrepository.com/artifact/io.micrometer/micrometer-commons/1.14.4
Apache-2.0
https://mvnrepository.com/artifact/io.micrometer/micrometer-core/1.14.4
Apache-2.0
https://mvnrepository.com/artifact/io.micrometer/micrometer-observation/1.14.4
Apache-2.0
@@ -369,6 +370,7 @@ The text of each license is the standard Apache 2.0 license.
https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-jdk8/1.6.4
Apache-2.0
https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-reactive/1.6.4
Apache-2.0
https://mvnrepository.com/artifact/org.jetbrains/annotations/13.0
Apache-2.0
+ https://mvnrepository.com/artifact/org.jspecify/jspecify/1.0.0 Apache-2.0
https://mvnrepository.com/artifact/org.lz4/lz4-java/1.8.0 Apache-2.0
https://mvnrepository.com/artifact/org.slf4j/jcl-over-slf4j/1.7.30
Apache-2.0
https://mvnrepository.com/artifact/org.slf4j/log4j-over-slf4j/1.7.30
Apache-2.0
@@ -543,7 +545,7 @@ The text of each license is also included in
licenses/LICENSE-[project].txt.
https://npmjs.com/package/nanoid/v/3.3.8 3.3.8 MIT
https://mvnrepository.com/artifact/org.checkerframework/checker-qual/3.33.0 MIT
https://mvnrepository.com/artifact/org.codehaus.mojo/animal-sniffer-annotations/1.24
MIT
-
https://mvnrepository.com/artifact/org.curioswitch.curiostack/protobuf-jackson/2.7.0
MIT
+
https://mvnrepository.com/artifact/org.curioswitch.curiostack/protobuf-jackson/2.8.1
MIT
https://mvnrepository.com/artifact/org.slf4j/slf4j-api/1.7.30 MIT
https://npmjs.com/package/pinia/v/2.0.28 2.0.28 MIT
https://npmjs.com/package/pinia/node_modules/vue-demi/v/0.13.11 0.13.11 MIT
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index 66b7a3427c..2803d047c0 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -14,6 +14,7 @@
* Add `LatestLabeledFunction` for meter.
* MAL Labeled metrics support additional attributes.
* Bump up netty to 4.2.9.Final.
+* Add support for OpenSearch/ElasticSearch client certificate authentication.
#### UI
* Fix the missing icon in new native trace view.
diff --git a/docs/en/setup/backend/storages/elasticsearch.md
b/docs/en/setup/backend/storages/elasticsearch.md
index a2d60a5a73..2f6ec21ba5 100644
--- a/docs/en/setup/backend/storages/elasticsearch.md
+++ b/docs/en/setup/backend/storages/elasticsearch.md
@@ -11,7 +11,7 @@ In order to activate OpenSearch as storage, set the storage
provider to **elasti
We support and tested the following versions of OpenSearch:
-- 1.1.0, 1.3.10
+- 1.3.10
- 2.4.0, 2.8.0, 3.0.0
## Elasticsearch
@@ -51,6 +51,8 @@ storage:
protocol: ${SW_STORAGE_ES_HTTP_PROTOCOL:"http"}
trustStorePath: ${SW_STORAGE_ES_SSL_JKS_PATH:""}
trustStorePass: ${SW_STORAGE_ES_SSL_JKS_PASS:""}
+ keyStorePath: ${SW_STORAGE_ES_SSL_KEY_STORE_PATH:""} # Path to client
certificate keystore for mutual TLS (OpenSearch/Elasticsearch client cert
auth). Supports PKCS12 (.p12, .pfx) and JKS (.jks) formats.
+ keyStorePass: ${SW_STORAGE_ES_SSL_KEY_STORE_PASS:""} # Password for the
client certificate keystore. Can be managed via secretsManagementFile.
user: ${SW_ES_USER:""}
password: ${SW_ES_PASSWORD:""}
secretsManagementFile: ${SW_ES_SECRETS_MANAGEMENT_FILE:""} # Secrets
management file in the properties format includes the username, password, which
are managed by 3rd party tool.
@@ -83,7 +85,9 @@ storage:
enableCustomRouting: ${SW_STORAGE_ES_ENABLE_CUSTOM_ROUTING:false}
```
-### ElasticSearch With Https SSL Encrypting communications.
+### ElasticSearch/OpenSearch With HTTPS SSL Encrypting Communications
+
+#### Basic HTTPS with Server Certificate Verification
Example:
@@ -103,6 +107,32 @@ storage:
- File at `trustStorePath` is being monitored. Once it is changed, the
ElasticSearch client will reconnect.
- `trustStorePass` could be changed in the runtime through [**Secrets
Management File Of ElasticSearch
Authentication**](#secrets-management-file-of-elasticsearch-authentication).
+#### Mutual TLS (mTLS) with Client Certificate Authentication
+
+For enhanced security, you can configure mutual TLS where the client presents
a certificate to the server. This is commonly used with OpenSearch security
plugin's client certificate authentication.
+
+Example:
+
+```yaml
+storage:
+ selector: ${SW_STORAGE:elasticsearch}
+ elasticsearch:
+ namespace: ${SW_NAMESPACE:""}
+ clusterNodes: ${SW_STORAGE_ES_CLUSTER_NODES:localhost:9200}
+ protocol: ${SW_STORAGE_ES_HTTP_PROTOCOL:"https"}
+ trustStorePath: ${SW_STORAGE_ES_SSL_JKS_PATH:"../truststore.jks"}
+ trustStorePass: ${SW_STORAGE_ES_SSL_JKS_PASS:"changeit"}
+ keyStorePath: ${SW_STORAGE_ES_SSL_KEY_STORE_PATH:"../client.p12"}
+ keyStorePass: ${SW_STORAGE_ES_SSL_KEY_STORE_PASS:"changeit"}
+ ...
+```
+
+- `keyStorePath` points to the client certificate keystore file. Supports both
PKCS12 (`.p12`, `.pfx`) and JKS (`.jks`) formats.
+- `keyStorePass` is the password for the client keystore. Use empty string
`""` for keystores without password.
+- Both `trustStorePath` and `keyStorePath` files are being monitored. Once
they are changed, the ElasticSearch client will reconnect.
+- `trustStorePass` and `keyStorePass` could be changed in the runtime through
[**Secrets Management File Of ElasticSearch
Authentication**](#secrets-management-file-of-elasticsearch-authentication).
+- When `keyStorePath` is configured, `keyStorePass` must also be provided (can
be empty string for no password).
+
### Daily Index Step
Daily index step(`storage/elasticsearch/dayStep`, default 1) represents the
index creation period. In this period, metrics for several days (dayStep value)
are saved.
@@ -121,17 +151,18 @@ NOTE: TTL deletion would be affected by these steps. You
should set an extra day
### Secrets Management File Of ElasticSearch Authentication
The value of `secretsManagementFile` should point to the secrets management
file absolute path.
-The file includes the username, password, and JKS password of the
ElasticSearch server in the properties format.
+The file includes the username, password, JKS password, and keystore password
of the ElasticSearch server in the properties format.
```properties
user=xxx
password=yyy
trustStorePass=zzz
+keyStorePass=aaa
```
-The major difference between using `user, password, trustStorePass` configs in
the `application.yaml` file is that the **Secrets Management File** is being
watched by the OAP server.
+The major difference between using `user, password, trustStorePass,
keyStorePass` configs in the `application.yaml` file is that the **Secrets
Management File** is being watched by the OAP server.
Once it is changed manually or through a 3rd party tool, such as
[Vault](https://github.com/hashicorp/vault),
-the storage provider will use the new username, password, and JKS password to
establish the connection and close the old one. If the information exists in
the file,
-the `user/password` will be overridden.
+the storage provider will use the new username, password, JKS password, and
keystore password to establish the connection and close the old one. If the
information exists in the file,
+the `user/password/trustStorePass/keyStorePass` will be overridden.
### Index Settings
diff --git a/oap-server-bom/pom.xml b/oap-server-bom/pom.xml
index f990e755c6..4a35af481e 100644
--- a/oap-server-bom/pom.xml
+++ b/oap-server-bom/pom.xml
@@ -67,7 +67,7 @@
<postgresql.version>42.4.4</postgresql.version>
<jetcd.version>0.6.1</jetcd.version>
<testcontainers.version>1.17.6</testcontainers.version>
- <armeria.version>1.32.0</armeria.version>
+ <armeria.version>1.34.2</armeria.version>
<awaitility.version>3.0.0</awaitility.version>
<httpcore.version>4.4.16</httpcore.version>
<httpasyncclient.version>4.1.5</httpasyncclient.version>
diff --git
a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ElasticSearchClient.java
b/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ElasticSearchClient.java
index 10330088ae..5a6a7933f5 100644
---
a/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ElasticSearchClient.java
+++
b/oap-server/server-library/library-client/src/main/java/org/apache/skywalking/oap/server/library/client/elasticsearch/ElasticSearchClient.java
@@ -76,6 +76,11 @@ public class ElasticSearchClient implements Client,
HealthCheckable {
@Setter
private volatile String trustStorePass;
+ private final String keyStorePath;
+
+ @Setter
+ private volatile String keyStorePass;
+
@Setter
private volatile String user;
@@ -107,6 +112,8 @@ public class ElasticSearchClient implements Client,
HealthCheckable {
String protocol,
String trustStorePath,
String trustStorePass,
+ String keyStorePath,
+ String keyStorePass,
String user,
String password,
Function<String, String> indexNameConverter,
@@ -119,6 +126,8 @@ public class ElasticSearchClient implements Client,
HealthCheckable {
this.protocol = protocol;
this.trustStorePath = trustStorePath;
this.trustStorePass = trustStorePass;
+ this.keyStorePath = keyStorePath;
+ this.keyStorePass = keyStorePass;
this.user = user;
this.password = password;
this.indexNameConverter = indexNameConverter;
@@ -152,9 +161,17 @@ public class ElasticSearchClient implements Client,
HealthCheckable {
if (!Strings.isNullOrEmpty(trustStorePath)) {
cb.trustStorePath(trustStorePath);
+ // Always set trustStorePass if trustStorePath is set (even if
empty string)
+ if (trustStorePass != null) {
+ cb.trustStorePass(trustStorePass);
+ }
}
- if (!Strings.isNullOrEmpty(trustStorePass)) {
- cb.trustStorePass(trustStorePass);
+ if (!Strings.isNullOrEmpty(keyStorePath)) {
+ cb.keyStorePath(keyStorePath);
+ // Always set keyStorePass if keyStorePath is set (even if empty
string)
+ if (keyStorePass != null) {
+ cb.keyStorePass(keyStorePass);
+ }
}
if (!Strings.isNullOrEmpty(user)) {
cb.username(user);
diff --git
a/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/library/elasticsearch/bulk/ElasticSearchIT.java
b/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/library/elasticsearch/bulk/ElasticSearchIT.java
index b7eefc669c..1cf99af3bf 100644
---
a/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/library/elasticsearch/bulk/ElasticSearchIT.java
+++
b/oap-server/server-library/library-client/src/test/java/org/apache/skywalking/library/elasticsearch/bulk/ElasticSearchIT.java
@@ -112,7 +112,7 @@ public class ElasticSearchIT {
final ElasticSearchClient client = new ElasticSearchClient(
moduleManager,
server.getHttpHostAddress(),
- "http", "", "", "test", "test",
+ "http", "", "", "", "", "test", "test",
indexNameConverter(namespace), 500, 6000,
0, 15
);
@@ -165,7 +165,7 @@ public class ElasticSearchIT {
final ElasticSearchClient client = new ElasticSearchClient(
moduleManager,
server.getHttpHostAddress(),
- "http", "", "", "test", "test",
+ "http", "", "", "", "", "test", "test",
indexNameConverter(namespace), 500, 6000,
0, 15
);
@@ -241,7 +241,7 @@ public class ElasticSearchIT {
final ElasticSearchClient client = new ElasticSearchClient(
moduleManager,
server.getHttpHostAddress(),
- "http", "", "", "test", "test",
+ "http", "", "", "", "", "test", "test",
indexNameConverter(namespace), 500, 6000,
0, 15
);
@@ -297,7 +297,7 @@ public class ElasticSearchIT {
final ElasticSearchClient client = new ElasticSearchClient(
moduleManager,
server.getHttpHostAddress(),
- "http", "", "", "test", "test",
+ "http", "", "", "", "", "test", "test",
indexNameConverter(namespace), 500, 6000,
0, 15
);
@@ -331,7 +331,7 @@ public class ElasticSearchIT {
final ElasticSearchClient client = new ElasticSearchClient(
moduleManager,
server.getHttpHostAddress(),
- "http", "", "", "test", "test",
+ "http", "", "", "", "", "test", "test",
indexNameConverter(namespace), 500, 6000,
0, 15
);
@@ -361,7 +361,7 @@ public class ElasticSearchIT {
final ElasticSearchClient client = new ElasticSearchClient(
moduleManager,
server.getHttpHostAddress(),
- "http", "", "", "test", "test",
+ "http", "", "", "", "", "test", "test",
indexNameConverter(namespace), 500, 6000,
0, 15
);
diff --git
a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/ElasticSearchBuilder.java
b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/ElasticSearchBuilder.java
index b85b749216..e4ab1243d0 100644
---
a/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/ElasticSearchBuilder.java
+++
b/oap-server/server-library/library-elasticsearch-client/src/main/java/org/apache/skywalking/library/elasticsearch/ElasticSearchBuilder.java
@@ -27,15 +27,19 @@ import
com.linecorp.armeria.client.endpoint.healthcheck.HealthCheckedEndpointGro
import com.linecorp.armeria.client.logging.LoggingClient;
import com.linecorp.armeria.common.SessionProtocol;
import com.linecorp.armeria.common.auth.AuthToken;
+
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;
+import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.Arrays;
+import java.util.Enumeration;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
+import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@@ -46,164 +50,284 @@ import
org.apache.skywalking.oap.server.library.util.StringUtil;
@Slf4j
public final class ElasticSearchBuilder {
- private static final int NUM_PROC =
Runtime.getRuntime().availableProcessors();
-
- private SessionProtocol protocol = SessionProtocol.HTTP;
-
- private String username;
-
- private String password;
+ private static final int NUM_PROC =
Runtime.getRuntime().availableProcessors();
- private Duration healthCheckRetryInterval = Duration.ofSeconds(30);
+ private SessionProtocol protocol = SessionProtocol.HTTP;
- private final ImmutableList.Builder<String> endpoints =
ImmutableList.builder();
+ private String username;
- private String trustStorePath;
+ private String password;
- private String trustStorePass;
+ private Duration healthCheckRetryInterval = Duration.ofSeconds(30);
- private Duration responseTimeout = Duration.ofSeconds(15);
+ private final ImmutableList.Builder<String> endpoints =
ImmutableList.builder();
- private Duration connectTimeout = Duration.ofMillis(500);
+ private String trustStorePath;
- private Duration socketTimeout = Duration.ofSeconds(30);
+ private String trustStorePass;
- private Consumer<Boolean> healthyListener;
+ private String keyStorePath;
- private int numHttpClientThread;
-
- public ElasticSearchBuilder protocol(String protocol) {
- checkArgument(StringUtil.isNotBlank(protocol), "protocol cannot be
blank");
- this.protocol = SessionProtocol.of(protocol);
- return this;
- }
-
- public ElasticSearchBuilder username(String username) {
- this.username = requireNonNull(username, "username");
- return this;
- }
+ private String keyStorePass;
- public ElasticSearchBuilder password(String password) {
- this.password = requireNonNull(password, "password");
- return this;
- }
-
- public ElasticSearchBuilder endpoints(Iterable<String> endpoints) {
- requireNonNull(endpoints, "endpoints");
- this.endpoints.addAll(endpoints);
- return this;
- }
-
- public ElasticSearchBuilder endpoints(String... endpoints) {
- return endpoints(Arrays.asList(endpoints));
- }
-
- public ElasticSearchBuilder healthCheckRetryInterval(Duration
healthCheckRetryInterval) {
- requireNonNull(healthCheckRetryInterval, "healthCheckRetryInterval");
- this.healthCheckRetryInterval = healthCheckRetryInterval;
- return this;
- }
+ private Duration responseTimeout = Duration.ofSeconds(15);
- public ElasticSearchBuilder trustStorePath(String trustStorePath) {
- requireNonNull(trustStorePath, "trustStorePath");
- this.trustStorePath = trustStorePath;
- return this;
- }
+ private Duration connectTimeout = Duration.ofMillis(500);
+
+ private Duration socketTimeout = Duration.ofSeconds(30);
+
+ private Consumer<Boolean> healthyListener;
+
+ private int numHttpClientThread;
+
+ public ElasticSearchBuilder protocol(String protocol) {
+ checkArgument(StringUtil.isNotBlank(protocol), "protocol cannot be blank");
+ this.protocol = SessionProtocol.of(protocol);
+ return this;
+ }
+
+ public ElasticSearchBuilder username(String username) {
+ this.username = requireNonNull(username, "username");
+ return this;
+ }
+
+ public ElasticSearchBuilder password(String password) {
+ this.password = requireNonNull(password, "password");
+ return this;
+ }
+
+ public ElasticSearchBuilder endpoints(Iterable<String> endpoints) {
+ requireNonNull(endpoints, "endpoints");
+ this.endpoints.addAll(endpoints);
+ return this;
+ }
+
+ public ElasticSearchBuilder endpoints(String... endpoints) {
+ return endpoints(Arrays.asList(endpoints));
+ }
+
+ public ElasticSearchBuilder healthCheckRetryInterval(Duration
healthCheckRetryInterval) {
+ requireNonNull(healthCheckRetryInterval, "healthCheckRetryInterval");
+ this.healthCheckRetryInterval = healthCheckRetryInterval;
+ return this;
+ }
+
+ public ElasticSearchBuilder trustStorePath(String trustStorePath) {
+ requireNonNull(trustStorePath, "trustStorePath");
+ this.trustStorePath = trustStorePath;
+ return this;
+ }
+
+ public ElasticSearchBuilder trustStorePass(String trustStorePass) {
+ requireNonNull(trustStorePass, "trustStorePass");
+ this.trustStorePass = trustStorePass;
+ return this;
+ }
+
+ public ElasticSearchBuilder keyStorePath(String keyStorePath) {
+ requireNonNull(keyStorePath, "keyStorePath");
+ this.keyStorePath = keyStorePath;
+ return this;
+ }
+
+ public ElasticSearchBuilder keyStorePass(String keyStorePass) {
+ requireNonNull(keyStorePass, "keyStorePass");
+ this.keyStorePass = keyStorePass;
+ return this;
+ }
+
+ public ElasticSearchBuilder connectTimeout(int connectTimeout) {
+ checkArgument(connectTimeout > 0, "connectTimeout must be positive");
+ this.connectTimeout = Duration.ofMillis(connectTimeout);
+ return this;
+ }
+
+ public ElasticSearchBuilder responseTimeout(int responseTimeout) {
+ checkArgument(responseTimeout >= 0, "responseTimeout must be 0 or
positive");
+ this.responseTimeout = Duration.ofMillis(responseTimeout);
+ return this;
+ }
+
+ public ElasticSearchBuilder socketTimeout(int socketTimeout) {
+ checkArgument(socketTimeout > 0, "socketTimeout must be positive");
+ this.socketTimeout = Duration.ofMillis(socketTimeout);
+ return this;
+ }
+
+ public ElasticSearchBuilder healthyListener(Consumer<Boolean>
healthyListener) {
+ requireNonNull(healthyListener, "healthyListener");
+ this.healthyListener = healthyListener;
+ return this;
+ }
+
+ public ElasticSearchBuilder numHttpClientThread(int numHttpClientThread) {
+ this.numHttpClientThread = numHttpClientThread;
+ return this;
+ }
+
+ @SneakyThrows
+ public ElasticSearch build() {
+ final List<Endpoint> endpoints = this.endpoints.build().stream()
+ .filter(StringUtil::isNotBlank)
+ .map(Endpoint::parse)
+ .collect(Collectors.toList());
+ final ClientFactoryBuilder factoryBuilder = ClientFactory.builder()
+ .connectTimeout(connectTimeout)
+ .idleTimeout(socketTimeout)
+ .useHttp2Preface(false)
+ .workerGroup(numHttpClientThread > 0 ? numHttpClientThread : NUM_PROC);
+
+ // Configure SSL/TLS with optional mutual TLS (client certificate
+ // authentication)
+ final boolean hasTrustStore = StringUtil.isNotBlank(trustStorePath);
+ final boolean hasKeyStore = StringUtil.isNotBlank(keyStorePath);
+
+ if (hasTrustStore || hasKeyStore) {
+ factoryBuilder.tlsCustomizer(sslContextBuilder -> {
+ try {
+ // Configure trust store for server certificate validation
+ if (hasTrustStore) {
+ log.info("Loading truststore from: {}", trustStorePath);
+ final var trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ final var truststoreType = detectKeystoreType(trustStorePath);
+ log.info("Detected truststore type: {} for file: {}",
truststoreType, trustStorePath);
+ final var truststore = KeyStore.getInstance(truststoreType);
+ try (final InputStream is =
Files.newInputStream(Paths.get(trustStorePath))) {
+ truststore.load(is, trustStorePass != null ?
trustStorePass.toCharArray() : null);
+ log.info("Successfully loaded truststore from: {}",
trustStorePath);
+ }
+ trustManagerFactory.init(truststore);
+ sslContextBuilder.trustManager(trustManagerFactory);
+ log.info("Truststore configured successfully");
+ }
+
+ // Configure key store for client certificate authentication (mutual
TLS)
+ if (hasKeyStore) {
+ log.info("Loading keystore from: {}", keyStorePath);
+ final var keyManagerFactory =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+
+ // Detect keystore type from file extension or try both PKCS12 and
JKS
+ final var keystoreType = detectKeystoreType(keyStorePath);
+ log.info("Detected keystore type: {} for file: {}", keystoreType,
keyStorePath);
+ final var keystore = KeyStore.getInstance(keystoreType);
+
+ try (final var is = Files.newInputStream(Paths.get(keyStorePath)))
{
+ keystore.load(is, keyStorePass != null ?
keyStorePass.toCharArray() : null);
+ log.info("Successfully loaded keystore from: {}", keyStorePath);
+ }
- public ElasticSearchBuilder trustStorePass(String trustStorePass) {
- requireNonNull(trustStorePass, "trustStorePass");
- this.trustStorePass = trustStorePass;
- return this;
- }
+ // Log certificate details for troubleshooting
+ logCertificateDetails(keystore, keyStorePath);
- public ElasticSearchBuilder connectTimeout(int connectTimeout) {
- checkArgument(connectTimeout > 0, "connectTimeout must be positive");
- this.connectTimeout = Duration.ofMillis(connectTimeout);
- return this;
- }
+ keyManagerFactory.init(keystore, keyStorePass != null ?
keyStorePass.toCharArray() : null);
+ sslContextBuilder.keyManager(keyManagerFactory);
- public ElasticSearchBuilder responseTimeout(int responseTimeout) {
- checkArgument(responseTimeout >= 0, "responseTimeout must be 0 or
positive");
- this.responseTimeout = Duration.ofMillis(responseTimeout);
- return this;
+ log.info("Client certificate authentication enabled with keystore:
{} (type: {})",
+ keyStorePath, keystoreType);
+ }
+ } catch (Exception e) {
+ log.error("Failed to configure SSL/TLS context", e);
+ throw new RuntimeException("Failed to configure SSL/TLS context: " +
e.getMessage(), e);
+ }
+ });
}
- public ElasticSearchBuilder socketTimeout(int socketTimeout) {
- checkArgument(socketTimeout > 0, "socketTimeout must be positive");
- this.socketTimeout = Duration.ofMillis(socketTimeout);
- return this;
+ final ClientFactory clientFactory = factoryBuilder.build();
+
+ final HealthCheckedEndpointGroupBuilder endpointGroupBuilder =
HealthCheckedEndpointGroup
+ .builder(EndpointGroup.of(endpoints), "_cluster/health")
+ .protocol(protocol)
+ .useGet(true)
+ .clientFactory(clientFactory)
+ .retryInterval(healthCheckRetryInterval)
+ .withClientOptions(options -> {
+ options.decorator(
+ LoggingClient.builder()
+ .logger(log)
+ .newDecorator());
+ options.decorator((delegate, ctx, req) -> {
+ ctx.logBuilder().name("health-check");
+ return delegate.execute(ctx, req);
+ });
+ return options;
+ });
+ if (StringUtil.isNotBlank(username) && StringUtil.isNotBlank(password)) {
+ endpointGroupBuilder.auth(AuthToken.ofBasic(username, password));
}
-
- public ElasticSearchBuilder healthyListener(Consumer<Boolean>
healthyListener) {
- requireNonNull(healthyListener, "healthyListener");
- this.healthyListener = healthyListener;
- return this;
+ final HealthCheckedEndpointGroup endpointGroup =
endpointGroupBuilder.build();
+
+ return new ElasticSearch(
+ protocol,
+ username,
+ password,
+ endpointGroup,
+ clientFactory,
+ healthyListener,
+ responseTimeout);
+ }
+
+ /**
+ * Detects keystore type from file extension.
+ * Defaults to PKCS12 as it's the modern standard and recommended format.
+ */
+ private static String detectKeystoreType(String keyStorePath) {
+ if (keyStorePath == null) {
+ return "PKCS12";
}
- public ElasticSearchBuilder numHttpClientThread(int numHttpClientThread) {
- this.numHttpClientThread = numHttpClientThread;
- return this;
+ String lowerPath = keyStorePath.toLowerCase();
+ if (lowerPath.endsWith(".jks")) {
+ return "JKS";
+ } else if (lowerPath.endsWith(".p12") || lowerPath.endsWith(".pfx")) {
+ return "PKCS12";
}
- @SneakyThrows
- public ElasticSearch build() {
- final List<Endpoint> endpoints =
- this.endpoints.build().stream()
- .filter(StringUtil::isNotBlank)
- .map(Endpoint::parse)
- .collect(Collectors.toList());
- final ClientFactoryBuilder factoryBuilder =
- ClientFactory.builder()
- .connectTimeout(connectTimeout)
- .idleTimeout(socketTimeout)
- .useHttp2Preface(false)
- .workerGroup(numHttpClientThread > 0 ?
numHttpClientThread : NUM_PROC);
-
- if (StringUtil.isNotBlank(trustStorePath)) {
- final TrustManagerFactory trustManagerFactory =
-
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- final KeyStore truststore = KeyStore.getInstance("jks");
- try (final InputStream is =
Files.newInputStream(Paths.get(trustStorePath))) {
- truststore.load(is, trustStorePass.toCharArray());
- }
- trustManagerFactory.init(truststore);
-
- factoryBuilder.tlsCustomizer(
- sslContextBuilder ->
sslContextBuilder.trustManager(trustManagerFactory));
- }
-
- final ClientFactory clientFactory = factoryBuilder.build();
-
- final HealthCheckedEndpointGroupBuilder endpointGroupBuilder =
- HealthCheckedEndpointGroup.builder(EndpointGroup.of(endpoints),
"_cluster/health")
- .protocol(protocol)
- .useGet(true)
- .clientFactory(clientFactory)
- .retryInterval(healthCheckRetryInterval)
- .withClientOptions(options -> {
- options.decorator(
- LoggingClient.builder()
- .logger(log)
- .newDecorator());
- options.decorator((delegate, ctx,
req) -> {
-
ctx.logBuilder().name("health-check");
- return delegate.execute(ctx,
req);
- });
- return options;
- });
- if (StringUtil.isNotBlank(username) &&
StringUtil.isNotBlank(password)) {
- endpointGroupBuilder.auth(AuthToken.ofBasic(username, password));
+ // Default to PKCS12 for unknown extensions
+ log.info("Unknown keystore extension for {}, defaulting to PKCS12 format",
keyStorePath);
+ return "PKCS12";
+ }
+
+ /**
+ * Logs certificate details from the keystore for troubleshooting purposes.
+ */
+ @SneakyThrows
+ private static void logCertificateDetails(KeyStore keystore, String
keyStorePath) {
+ try {
+ final Enumeration<String> aliases = keystore.aliases();
+ int certCount = 0;
+ while (aliases.hasMoreElements()) {
+ final String alias = aliases.nextElement();
+ certCount++;
+ log.info("Keystore entry [{}]: alias='{}', isKeyEntry={},
isCertificateEntry={}",
+ certCount, alias, keystore.isKeyEntry(alias),
keystore.isCertificateEntry(alias));
+
+ if (keystore.isKeyEntry(alias)) {
+ final java.security.cert.Certificate cert =
keystore.getCertificate(alias);
+ if (cert instanceof X509Certificate) {
+ final X509Certificate x509Cert = (X509Certificate) cert;
+ log.info(" Certificate subject: {}", x509Cert.getSubjectDN());
+ log.info(" Certificate issuer: {}", x509Cert.getIssuerDN());
+ log.info(" Certificate serial number: {}",
x509Cert.getSerialNumber());
+ log.info(" Certificate valid from: {} to {}",
+ x509Cert.getNotBefore(), x509Cert.getNotAfter());
+ log.info(" Certificate algorithm: {}", x509Cert.getSigAlgName());
+ }
+ } else if (keystore.isCertificateEntry(alias)) {
+ final java.security.cert.Certificate cert =
keystore.getCertificate(alias);
+ if (cert instanceof X509Certificate) {
+ final X509Certificate x509Cert = (X509Certificate) cert;
+ log.info(" Certificate subject: {}", x509Cert.getSubjectDN());
+ log.info(" Certificate issuer: {}", x509Cert.getIssuerDN());
+ }
}
- final HealthCheckedEndpointGroup endpointGroup =
endpointGroupBuilder.build();
-
- return new ElasticSearch(
- protocol,
- username,
- password,
- endpointGroup,
- clientFactory,
- healthyListener,
- responseTimeout
- );
+ }
+ if (certCount == 0) {
+ log.warn("No certificates found in keystore: {}", keyStorePath);
+ } else {
+ log.info("Total entries in keystore: {}", certCount);
+ }
+ } catch (Exception e) {
+ log.warn("Failed to log certificate details from keystore: {}",
keyStorePath, e);
}
+ }
}
diff --git
a/oap-server/server-library/library-elasticsearch-client/src/test/java/org/apache/skywalking/library/elasticsearch/ElasticSearchIT.java
b/oap-server/server-library/library-elasticsearch-client/src/test/java/org/apache/skywalking/library/elasticsearch/ElasticSearchIT.java
index bc1130832d..295a9eb81a 100644
---
a/oap-server/server-library/library-elasticsearch-client/src/test/java/org/apache/skywalking/library/elasticsearch/ElasticSearchIT.java
+++
b/oap-server/server-library/library-elasticsearch-client/src/test/java/org/apache/skywalking/library/elasticsearch/ElasticSearchIT.java
@@ -422,4 +422,25 @@ public class ElasticSearchIT {
assertFalse(client.documents().exists(index, type, idWithSpace));
server.close();
}
+
+ @ParameterizedTest(name = "version: {0}")
+ @MethodSource("es")
+ public void testClientBuilder(final String ignored,
+ final ElasticsearchContainer server) {
+ server.start();
+
+ // Test basic builder functionality
+ final ElasticSearch client =
+ ElasticSearch.builder()
+ .endpoints(server.getHttpHostAddress())
+ .build();
+ client.connect();
+
+ final String index = "test-builder-index";
+ assertTrue(client.index().create(index, null, null));
+ assertTrue(client.index().exists(index));
+ assertTrue(client.index().delete(index));
+
+ server.close();
+ }
}
diff --git a/oap-server/server-starter/src/main/resources/application.yml
b/oap-server/server-starter/src/main/resources/application.yml
index fd6356f203..f62259e19a 100644
--- a/oap-server/server-starter/src/main/resources/application.yml
+++ b/oap-server/server-starter/src/main/resources/application.yml
@@ -158,8 +158,10 @@ storage:
numHttpClientThread: ${SW_STORAGE_ES_NUM_HTTP_CLIENT_THREAD:0}
user: ${SW_ES_USER:""}
password: ${SW_ES_PASSWORD:""}
- trustStorePath: ${SW_STORAGE_ES_SSL_JKS_PATH:""}
- trustStorePass: ${SW_STORAGE_ES_SSL_JKS_PASS:""}
+ trustStorePath: ${SW_STORAGE_ES_SSL_JKS_PATH:""} # Path to the truststore
that contains CA certificates used to verify the OpenSearch/Elasticsearch
server certificate.
+ trustStorePass: ${SW_STORAGE_ES_SSL_JKS_PASS:""} # Password for the
truststore defined in trustStorePath.
+ keyStorePath: ${SW_STORAGE_ES_SSL_KEY_STORE_PATH:""} # Path to the client
certificate keystore for mutual TLS (OpenSearch/Elasticsearch client
certificate authentication).
+ keyStorePass: ${SW_STORAGE_ES_SSL_KEY_STORE_PASS:""} # Password for the
client certificate keystore defined in keyStorePath. This can be externalized
via secretsManagementFile.
secretsManagementFile: ${SW_ES_SECRETS_MANAGEMENT_FILE:""} # Secrets
management file in the properties format includes the username, password, which
are managed by 3rd party tool.
dayStep: ${SW_STORAGE_DAY_STEP:1} # Represent the number of days in the
one minute/hour/day index.
indexShardsNumber: ${SW_STORAGE_ES_INDEX_SHARDS_NUMBER:1} # Shard number
of new indexes
diff --git
a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/StorageModuleElasticsearchConfig.java
b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/StorageModuleElasticsearchConfig.java
index d8dc848be5..606e131ba7 100644
---
a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/StorageModuleElasticsearchConfig.java
+++
b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/StorageModuleElasticsearchConfig.java
@@ -113,6 +113,19 @@ public class StorageModuleElasticsearchConfig extends
ModuleConfig {
* @since 7.0.0 This could be managed inside {@link #secretsManagementFile}
*/
private String trustStorePass;
+ /**
+ * Path to the keystore file for client certificate authentication
(OpenSearch/Elasticsearch mTLS).
+ * This enables mutual TLS where the client presents a certificate to the
server.
+ * Supports PKCS12 (.p12, .pfx) and JKS (.jks) formats.
+ * When set, {@link #keyStorePass} must also be provided.
+ */
+ private String keyStorePath;
+ /**
+ * Password for the keystore file specified in {@link #keyStorePath}.
+ * Required when {@link #keyStorePath} is set.
+ * This could be managed inside {@link #secretsManagementFile}
+ */
+ private String keyStorePass;
private int resultWindowMaxSize = 10000;
private int metadataQueryMaxSize = 5000;
/**
diff --git
a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/StorageModuleElasticsearchProvider.java
b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/StorageModuleElasticsearchProvider.java
index 3cfd5e014c..aad41665f6 100644
---
a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/StorageModuleElasticsearchProvider.java
+++
b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/StorageModuleElasticsearchProvider.java
@@ -179,6 +179,7 @@ public class StorageModuleElasticsearchProvider extends
ModuleProvider {
config.setUser(secrets.getProperty("user", null));
config.setPassword(secrets.getProperty("password", null));
config.setTrustStorePass(secrets.getProperty("trustStorePass",
null));
+ config.setKeyStorePass(secrets.getProperty("keyStorePass",
null));
if (elasticSearchClient == null) {
// In the startup process, we just need to change the
username/password
@@ -187,20 +188,27 @@ public class StorageModuleElasticsearchProvider extends
ModuleProvider {
elasticSearchClient.setUser(config.getUser());
elasticSearchClient.setPassword(config.getPassword());
elasticSearchClient.setTrustStorePass(config.getTrustStorePass());
+
elasticSearchClient.setKeyStorePass(config.getKeyStorePass());
elasticSearchClient.connect();
}
- }, config.getSecretsManagementFile(), config.getTrustStorePath());
+ }, config.getSecretsManagementFile(), config.getTrustStorePath(),
config.getKeyStorePath());
/*
* By leveraging the sync update check feature when startup.
*/
monitor.start();
}
+ // Validate keystore configuration
+ validateKeystoreConfiguration(config);
+
elasticSearchClient = new ElasticSearchClient(
getManager(),
- config.getClusterNodes(), config.getProtocol(),
config.getTrustStorePath(), config
- .getTrustStorePass(), config.getUser(), config.getPassword(),
- indexNameConverter(config.getNamespace()),
config.getConnectTimeout(),
+ config.getClusterNodes(), config.getProtocol(),
+ config.getTrustStorePath(), config.getTrustStorePass(),
+ config.getKeyStorePath(), config.getKeyStorePass(),
+ config.getUser(), config.getPassword(),
+ indexNameConverter(config.getNamespace()),
+ config.getConnectTimeout(),
config.getSocketTimeout(), config.getResponseTimeout(),
config.getNumHttpClientThread()
);
@@ -346,4 +354,29 @@ public class StorageModuleElasticsearchProvider extends
ModuleProvider {
return indexName;
};
}
+
+ /**
+ * Validates keystore configuration to ensure mutual TLS setup is complete.
+ * If keyStorePath is provided, keyStorePass must also be provided (can be
empty string for no password).
+ */
+ private void
validateKeystoreConfiguration(StorageModuleElasticsearchConfig config) {
+ final boolean hasKeyStorePath =
!StringUtil.isEmpty(config.getKeyStorePath());
+ final boolean hasKeyStorePass = config.getKeyStorePass() != null;
+
+ if (hasKeyStorePath && !hasKeyStorePass) {
+ throw new IllegalArgumentException(
+ "keyStorePass must be provided when keyStorePath is configured
for client certificate authentication. " +
+ "Use empty string (\"\") for keystores without password."
+ );
+ }
+
+ if (!hasKeyStorePath && hasKeyStorePass) {
+ log.warn("keyStorePass is configured but keyStorePath is not set.
The keyStorePass will be ignored.");
+ }
+
+ if (hasKeyStorePath) {
+ log.info("Client certificate authentication (mutual TLS) is
enabled with keystore: {}",
+ config.getKeyStorePath());
+ }
+ }
}
diff --git a/test/e2e-v2/cases/storage/opensearch/certs/admin-key.pem
b/test/e2e-v2/cases/storage/opensearch/certs/admin-key.pem
new file mode 100644
index 0000000000..c57777dedb
--- /dev/null
+++ b/test/e2e-v2/cases/storage/opensearch/certs/admin-key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+b8X+z9oYTUsT
+UfsKWr+dtGKHVEStllH9QmcTGZOkEsm3ERY+78WZgNhN/7mV02OeU9T7/ooQim1i
+dN0VgF7bm1iFDKilqRTlJqYZZF8xcgPr++x2vDlLANG+Qce58RoWdzDpKO1u96XT
+Z8nJpQWtSBfYBmxjNiQwiUueuXKT1grVEijtLNC/Fpa4I2SJUjI62/pi8XyQiPHL
+nW1PbJdkb7y+DcKomJAarsGd1ar+tVR6CZUpLztYLKW3oPwCNeC3TV63KV4f+/9D
+Hn6ENAbaVhiR/2YdrvU+XcZJ3apuSIBy4mKfSIH16I9iOp8Kuteo39M2f577JcoP
+bWaMi44DAgMBAAECggEALtKJknSlcXszncz21d1pJovO++oWtr1ybDwS3snXmKw7
+52RunUvTwEHDLS5WgYWHhUqkX87+QEHg0ifcoeg9qm4gDhqGLrELX6ooha69jwky
++KcoxSrTRWMursI6qreii+qDXph/BF0kav2mSgtmgWvr3OP7a0tJC5v+OUjsaHd3
+/4S1FFPXdza/0XAoBwMuMvG5arNooGttdgGknsPCJjEliPKYnLBZakFGCgZLZltX
+HGJpD9lvPp1XOCMpRrcF6spcW2/t70SR8zGj3VUckxaNKVt06i8Lqa4Up6dKhF8x
+rh3TMUol41B7lEHdmZQ3tBoXyuxJkBYr7M9egAFiXQKBgQDd0P/0MnYVVOUAL4cv
+2l2ywWxjlu421sZZ9Hf4sSrD65tJHJS4SqeUj2OVIbrCZ0G/lFBEva/3NYcZD4R2
+jyIQnWJOjemJAhTe6x4NKnJHqWXaZU61GGdTnLwHxcc9/sZSLd+wunOwzlCviPbR
+ReMgJhWJ9/XcFIY7slcB0KpkrQKBgQDbyMrWsycXIZ36hntPytuZZHtU/CdxSpR3
+XCEZtF/KdvVbzmZnW9Y+HDj4XqUOykuDrg3wTjmPI3SgjEVURbjraBJFSJUeF4w/
+XhG6K6Tx1Cmdqqih3q6nrkxOD9tiN6tT+R/wQfUOtenrCZhLcj9aZ//eIcHLyrSf
+WvpMwQdjbwKBgAxakYbGMLFrcv2ZqAvQO5uzDhhV1ZqUR6PG68+b/me+/X0K7HV/
+IuoxOjiaEk61dYH3/qh1cBFyl72bkaMQwbLvMQRy/ui0hvkLWzccgBThqFyLe+C2
+JTsQ5aABMeGQCPeWunibSco1E2VTWXu6SrYFqPlwJ+9D7V3xxsrBFlxZAoGAAh9x
+XhuC4CVR+k58OGwULOocitiYpO58ep6oLzBf0HvPqOBYet0XN6hcIIIBhCAOFKqE
+tfJ7edd00+wm60Z1H8j0jDjEP/MoRqBo+Wxcfn13HW+9izq0Yyg60nIyw0MYY4o/
+dbmdXVQCe2OvVeM3m27vuLyIu6gskHF3g3BF2v8CgYEAiNC7qcySASSWHDhSmGSv
+Xx/AXP1jGzrqb3BT0+0Dexxc+lumTqVCFWFcJmAgazgMThE4VLYZ1pK5KS9FwZHX
+HVSQluPNTy25DJ+pSXnNMtOiG8g+C61H7G1yQ+dS1QwH8f8/iVFEZ1dgVIxv5nV4
+Vxm2zOKeAF23sfOBkmYb15Y=
+-----END PRIVATE KEY-----
diff --git a/test/e2e-v2/cases/storage/opensearch/certs/admin.pem
b/test/e2e-v2/cases/storage/opensearch/certs/admin.pem
new file mode 100644
index 0000000000..c438f30db9
--- /dev/null
+++ b/test/e2e-v2/cases/storage/opensearch/certs/admin.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDTjCCAjYCFE5mcJsLUgt96WKdeQWD/6Pw52bAMA0GCSqGSIb3DQEBCwUAMGox
+CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTENMAsGA1UEBwwEVGVzdDETMBEGA1UE
+CgwKU2t5V2Fsa2luZzENMAsGA1UECwwEVGVzdDEbMBkGA1UEAwwSU2t5V2Fsa2lu
+ZyBSb290IENBMB4XDTI2MDEwNDEyMjY0NloXDTI4MDEwNDEyMjY0NlowXTELMAkG
+A1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ0wCwYDVQQHDARUZXN0MRMwEQYDVQQKDApT
+a3lXYWxraW5nMQ0wCwYDVQQLDARUZXN0MQ4wDAYDVQQDDAVhZG1pbjCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAL5vxf7P2hhNSxNR+wpav520YodURK2W
+Uf1CZxMZk6QSybcRFj7vxZmA2E3/uZXTY55T1Pv+ihCKbWJ03RWAXtubWIUMqKWp
+FOUmphlkXzFyA+v77Ha8OUsA0b5Bx7nxGhZ3MOko7W73pdNnycmlBa1IF9gGbGM2
+JDCJS565cpPWCtUSKO0s0L8WlrgjZIlSMjrb+mLxfJCI8cudbU9sl2RvvL4NwqiY
+kBquwZ3Vqv61VHoJlSkvO1gspbeg/AI14LdNXrcpXh/7/0MefoQ0BtpWGJH/Zh2u
+9T5dxkndqm5IgHLiYp9IgfXoj2I6nwq616jf0zZ/nvslyg9tZoyLjgMCAwEAATAN
+BgkqhkiG9w0BAQsFAAOCAQEAaukSpVdgSv5qtfcTH5spKH7TLClEshJukHXhnWAR
+ElMfua1/4+eP0KlW778N8uDsFHqjfCnXHnrzvgju0uXs+nl4eqUwloJ3WbwHnt+b
+6ArseyGF8ybn6Oc0IiYX3cCoxr0DxFh6Ft5I9H0oS5rohjp6aIpFd+ZUXRMFchgf
++NiZtPgaGvQk94WoHIoYeF44E8VQTdFu80mBWj0PhUGe4dXy1lnoUUBUcGOG0f3I
+QXHH2PP7nALBSEyluK23hUObmE8wqmLWl/M7se5kDgCndl/6zD8z9KFfUbWMaKzz
+n3JDGh+S92i4lHIxp4JJhru0zh/ZmOhbCyPiF9i5gyREyA==
+-----END CERTIFICATE-----
diff --git a/test/e2e-v2/cases/storage/opensearch/certs/client-key.pem
b/test/e2e-v2/cases/storage/opensearch/certs/client-key.pem
new file mode 100644
index 0000000000..718d67cc39
--- /dev/null
+++ b/test/e2e-v2/cases/storage/opensearch/certs/client-key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC5sz1zVe77Wk+L
+hJyoN4RbCYdThbLIse//qyS9V+Kid1RavHlic5xlWBJRs0t9xmDWsI9pxybKz0Cw
+NuuKx/yk2zZv2dChMf6NY+K8AsHj8lSVrIqeQHJq9oq94IWwanwcibCkuJLad+pU
+q6bFHqp629eopm8iMAvbeqKTVlMVKcg6VEhiiCm5pnh5OdiDcYRd92weLTpNUnw+
+exnYhbcjcO6sLUqEbf7cEFga4rwtsGDE9toKoL9aMhvhOGKE179fX07Y0hqNPJjG
+i43KhOeU5ND5radsMoi0yctp8Mpkx32xFSFwh9zLKSmN8QqIPJsIKrl+Rn7b0P7K
+Z4QT6UTxAgMBAAECggEAAqHTKKQZuR4ibHl+6jA+rGQT58dMJDEELono1/LbtB2P
+NZqvU3q54awIp/gCX8IWtqAUEubT2Ke/tZVaaBMiKQqv/FQkpEqgojUnG2sVO/i0
+uIsJPEAlodrv5hABKGWA0eElGRszK6zVL/MUQthVxKdr+248JHew51Wxf9lWSXYz
+5I81Oc2iZPY0AyT0pmMqQ6PxUS7xWfUx/vkXujvNEzn48qij7T9nPUlrE6olZgy2
+APzBMI0ws+KrUCv4jAfAaedtMbco4TxOt4LMKaMgXmiNlrKicCnIo4XY6vQuJCop
+BPV3aPscj7zDVwCEtQKpitUNEEjoMr3QYH02TSOtJQKBgQDgN8x06nRle061GucV
+GYZj1Ax/IohNRa0tuwr7WJn1Mky691r+jaIUfjRugFEsaFJmkR9UbZbAC/69RrSn
+8i2nhhHCRi6TUFxKQ4CRm/0BlvwXuH7SowNvTWgHvPeax4ztqFG3xMmtWs2RV6Dm
+zLcH1jVulKvtfFEvsPvMwb5uVQKBgQDUBb65Fi1RP6hLXS5fp2zh+jT5GTCq0bLp
+j8tv6iPBI9k02XqE3PNLqlPSgz+aBORhUIyZPuT1Qx1/tbDmuBxPntNETjpl5GwF
+qTxyFQIOAdZjy0eWpOIWM+lo9xEtJv3oN7TIMQHueXS4edrNsmYIvAel2lIjRH8Z
+fCt6G9BgLQKBgBBP/vACnrVDY1aJvoYqdTyOENqvCHuWtiK9mO7wY0MThcGUfWpH
+o6MaC3Z+n2k7rcMIi974mh8ewEnE+x+83tVxS5l2way2DADbKF9vmdijw3N2WMO6
+WGWgnBD0Do+UNQyVUlysVH/oO0x3s50XB7nqO7jv2BJPGRj/J1KeRdyBAoGBAKWG
+BqvAoIh5xg1wJbAPqXWSPKDsBY6WP7MPy6cHh/pU3lHgJ0JqrJY5107VoGXBw/ol
+RF6vN1gymWkGk6DLw251dEIzQGwjtCGHSeVWeVAuJw2pua3l84uZ43NKz2IMutT8
+CGrxt6xRrcoHd8Z2rCnNgbr9gnp+Eyv2QIsIA9nRAoGBAIVAyGg2Q6VNZzErWB21
++Cue68YF46VEiTXKhzOBawj1oeu2YAEFUO4jQQk1Z/Lng8UJF7hd7v8ALEcEKmS5
+Stmyq6wLAHqkQdF7o4r8c5ILSRo6hjCFXJ7+Zl+WbBuKrpgQd11eUXilUymh/cRM
+IluIOndu6l2Fo5ZEaW5EvKJb
+-----END PRIVATE KEY-----
diff --git a/test/e2e-v2/cases/storage/opensearch/certs/client.p12
b/test/e2e-v2/cases/storage/opensearch/certs/client.p12
new file mode 100644
index 0000000000..0b1802049d
Binary files /dev/null and
b/test/e2e-v2/cases/storage/opensearch/certs/client.p12 differ
diff --git a/test/e2e-v2/cases/storage/opensearch/certs/client.pem
b/test/e2e-v2/cases/storage/opensearch/certs/client.pem
new file mode 100644
index 0000000000..08078f74e5
--- /dev/null
+++ b/test/e2e-v2/cases/storage/opensearch/certs/client.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWzCCAkMCFE5mcJsLUgt96WKdeQWD/6Pw52bBMA0GCSqGSIb3DQEBCwUAMGox
+CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTENMAsGA1UEBwwEVGVzdDETMBEGA1UE
+CgwKU2t5V2Fsa2luZzENMAsGA1UECwwEVGVzdDEbMBkGA1UEAwwSU2t5V2Fsa2lu
+ZyBSb290IENBMB4XDTI2MDEwNDEyMjY0NloXDTI4MDEwNDEyMjY0NlowajELMAkG
+A1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ0wCwYDVQQHDARUZXN0MRMwEQYDVQQKDApT
+a3lXYWxraW5nMQ0wCwYDVQQLDARUZXN0MRswGQYDVQQDDBJub2RlLTAuZXhhbXBs
+ZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5sz1zVe77Wk+L
+hJyoN4RbCYdThbLIse//qyS9V+Kid1RavHlic5xlWBJRs0t9xmDWsI9pxybKz0Cw
+NuuKx/yk2zZv2dChMf6NY+K8AsHj8lSVrIqeQHJq9oq94IWwanwcibCkuJLad+pU
+q6bFHqp629eopm8iMAvbeqKTVlMVKcg6VEhiiCm5pnh5OdiDcYRd92weLTpNUnw+
+exnYhbcjcO6sLUqEbf7cEFga4rwtsGDE9toKoL9aMhvhOGKE179fX07Y0hqNPJjG
+i43KhOeU5ND5radsMoi0yctp8Mpkx32xFSFwh9zLKSmN8QqIPJsIKrl+Rn7b0P7K
+Z4QT6UTxAgMBAAEwDQYJKoZIhvcNAQELBQADggEBALlyJEE/xNkkCUzehlSYSs+o
++/BK5sC56sUE2y6Ar7bYhaeHpbh4SFUeow16+9cYgqLvz91PRQU/Iub+F31Bg9Vw
+lnaAC/pT+14CUAyXbtSklWBtlqGqWQjZxVgjOrop96Ygnte5d90ppD2+mxcju1Lc
+8iKBISR3mIOY9DVNj/G89FuRytX1vJ7kMvjVrbRcG6Du997a/NnMzolWWu4YERg9
+xHuSfAaXWVGmL7iMT3Czz/Jqv2nleb15OO0J2+XJkRZqA266xyG5T4vIew+YV7qM
+wqK2KasrciVttQKHhiJMpy0PmjrsxkyIe6nAR0T3mMB9OxPkIBNbqvNMRjsJy2I=
+-----END CERTIFICATE-----
diff --git a/test/e2e-v2/cases/storage/opensearch/certs/node-key.pem
b/test/e2e-v2/cases/storage/opensearch/certs/node-key.pem
new file mode 100644
index 0000000000..575d28a624
--- /dev/null
+++ b/test/e2e-v2/cases/storage/opensearch/certs/node-key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCVvg/acsfUtigU
+sxqUNBS/KV/4adtEYEB9V4mt4G+Q5N7PHAoyHakyspjDHZb+IHWYnRl4zOhIcMFZ
+9MOC/NQw//nYbRs5oB3YlZbZLfC4MCS6jKszkpA1LPuMvcCGz271SScu8/DXLiLV
+GbwXseJC4w9Qxbi7RMXRkCGhDcmm+Eg0JpaArYZUsPLiRRhP95LjDhtacqoPozO2
+/0y2ZvLu1vQyNpQQmjuL0BxHmQeXjFe4AEJ8ZLgbTPg2Cyu8XFJKf1bsJcH4xowc
+8nAugOTR3CV1aYvwUg+a+M9GOZPD+jdsac/kRfYJDBPf3ydKx7mPHAJh70GftG/y
+bMoD0NZRAgMBAAECggEACejPAcTQxpnbYytLGf2sVc42JKlUslywR9swKEqO94a9
+2/TeE9z0on9BLsBZiq25OoVC90SQJqMXmI1jFtsGa8u0zAbEY3beIsQbfHnW0UmW
+VKYUelA7rNkyOGkiQYmerSrPJgNMr+DEu1d9pA/IimaeT9kV1YbClJC2OQNBkgQI
+TfDV1FqO+6HyE//5WrG32wgEB/YDu80WSf13eIE9qrFqzzb6WXCYP4gT7WASvZRH
+lnCU08EFJKT6jVMmrizTO+tCqT2A9G0R/ASYylYS5W73+8rLJBd1g8uuHG9PGeB0
+NWvRxn6I3i/gHaJ2ltA5Fhy09zi8lEfwKiu5HxUUZQKBgQDKFAlrcv56XtNhIzup
+jah4tEdLOPwehIKJ+1qEQu0QXglWFivShj+WhrM6wy1VOgWgcCLsx9ZuQigUFFKM
+D+NsIaz277XSKbqE46KZJy+IzgSTzWUFlgDqzpfHqgW9XpiJzxOqd9h5QmXbW9Zc
+N75WTgFRfp1/Ig2lCD5uP7aBjwKBgQC9svb9xMM2zX7KAI3ADwovcnz3ca2TheKA
+li0MkeRcDAvrojkPE8WtzrrPXoZ892iLFgktEy9hEMyiW5XMKSqa6kh0JM1OUKsz
+In4/GNtZzRKwe4ZFIoBFP4d2OE3WNT3nHT8OFuEZkWwPsrDFVRlUtsHZzEHkqpl8
+ObJxjvd6HwKBgQCyrAiwIpry48kOSDLGdeQR5YRr9FSnPw6UpdOgwfQN1rd2kF/q
+4pxyoWLzgAMjKgwzkTKwHPlxv7jkGBvsj1fMEfJ22/ftfMvYF9V6iPU0hsPxU1gR
+GlJxSn1VIvW0PGGu55NB1HlordaVn5vnKbp3YL01qzfiYt+hnaplnJvn7QKBgFvj
+zyUKJQ3s6RfswL1iC6sEKGislko5tohXNqc6HIZCB5wyzrTw/Pa+h1tgDIGITwng
+uL0u5+p6+sVC3AMzhcHY7xPjp9fh16xDbygdYFPVtNHsZBQlLEFfDr1DdODolX3Y
+euzWRF/gQ5ovEtXj7QtOJATenqSnxwWX5UqA2Hw9AoGARRQ/RoxCrjtPOInC85Zg
+rl1qr0WmBwws8CarOI3glOnUHrZ6oH/fmWCMzNg+i5Z35BMqBY/w6LsiefYc+eGm
+sbj3lulzN+duR6K6HSHX6ipsokS5yVqPdYlfUfrqDHzTJpwHmypneZxJPubEm8z9
+Xy4Mfwseqj+aW5hmlzk9eFs=
+-----END PRIVATE KEY-----
diff --git a/test/e2e-v2/cases/storage/opensearch/certs/node.pem
b/test/e2e-v2/cases/storage/opensearch/certs/node.pem
new file mode 100644
index 0000000000..d364ee00c2
--- /dev/null
+++ b/test/e2e-v2/cases/storage/opensearch/certs/node.pem
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDxDCCAqygAwIBAgIUTmZwmwtSC33pYp15BYP/o/DnZr8wDQYJKoZIhvcNAQEL
+BQAwajELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ0wCwYDVQQHDARUZXN0MRMw
+EQYDVQQKDApTa3lXYWxraW5nMQ0wCwYDVQQLDARUZXN0MRswGQYDVQQDDBJTa3lX
+YWxraW5nIFJvb3QgQ0EwHhcNMjYwMTA0MTIyNjQ2WhcNMjgwMTA0MTIyNjQ2WjBi
+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExDTALBgNVBAcMBFRlc3QxEzARBgNV
+BAoMClNreVdhbGtpbmcxDTALBgNVBAsMBFRlc3QxEzARBgNVBAMMCm9wZW5zZWFy
+Y2gwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVvg/acsfUtigUsxqU
+NBS/KV/4adtEYEB9V4mt4G+Q5N7PHAoyHakyspjDHZb+IHWYnRl4zOhIcMFZ9MOC
+/NQw//nYbRs5oB3YlZbZLfC4MCS6jKszkpA1LPuMvcCGz271SScu8/DXLiLVGbwX
+seJC4w9Qxbi7RMXRkCGhDcmm+Eg0JpaArYZUsPLiRRhP95LjDhtacqoPozO2/0y2
+ZvLu1vQyNpQQmjuL0BxHmQeXjFe4AEJ8ZLgbTPg2Cyu8XFJKf1bsJcH4xowc8nAu
+gOTR3CV1aYvwUg+a+M9GOZPD+jdsac/kRfYJDBPf3ydKx7mPHAJh70GftG/ybMoD
+0NZRAgMBAAGjajBoMCYGA1UdEQQfMB2CCm9wZW5zZWFyY2iCCWxvY2FsaG9zdIcE
+fwAAATAdBgNVHQ4EFgQUWBLZsHgz29wzfO/jeRawe2mhx0EwHwYDVR0jBBgwFoAU
+gOjBnhpAdisZ/mzDyMgw5XCuNvcwDQYJKoZIhvcNAQELBQADggEBAMpdZPWisHxn
+3tMaLxH0kbiK+Kftws8EKRd8IYATK/NVvDRZyiKFgUNJEvADAzKTHqq5S8QTB6/U
+6s0Uu2kqkRUQdFuMkswEFqnP/dcYohJtT6Sa3cbOl32GDvoGQOGPd86Hj7SyeoIC
+MG1wmfOzv/COXuynecXUBDSMFRQ3kbClEHH2ELNMaYiWmU13tKA01FA7sWNOyiNR
+hJ72WSb2SkuF5GsEJjpo7EGtwerZHMDYDWoQfWgVaM1jSewOyy/Z5V/e8WnFqRgd
+p4MFyqGHuaFZRrR3+/LYQpBbV77dP+A+HltUieg/QvcarW5hBXlvlJDyvzMkPW0e
+7jv0Fh0jQhc=
+-----END CERTIFICATE-----
diff --git a/test/e2e-v2/cases/storage/opensearch/certs/root-ca-key.pem
b/test/e2e-v2/cases/storage/opensearch/certs/root-ca-key.pem
new file mode 100644
index 0000000000..6d03332c40
--- /dev/null
+++ b/test/e2e-v2/cases/storage/opensearch/certs/root-ca-key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDaV7mh/uKfwe9L
+sgvW834oBai9YNj7OGBM0inKWHQfO78OUwM9ri6EXgbxNnIbmvPPyX6ciUFuo9+Q
++Q+7mlyAvUyPGn7eomFa32ViOjvIHl9mtF4SvWcZ8yfSJdzFf/8Ul1OPxKM8MiCM
+mr6B6EqSUD+L/LNDyA+j9SCctwoPgpXljCkF2rfQepu51tw6RelokQ3dNoUQE0gL
+3FajOoZlbdGBX2h3Esik2h8bIIaZ8KM4at4ldr1nrsLY71nXqTGAsO/2/loF6Ccv
+wgnGqQYdkwR2u6y4U0B9K2m4c7lJ1+g/SiTBoDaSnXhwJxOujRACekPGKsT8p1+m
+saWK4OKBAgMBAAECggEADEbOprejCWvvmjhE5ycIt5P3ev9B8M1byMxjHvMrQ4Hp
+/UWyOUEc13XfLdsR8Z015HLsl6Ocb+3VFhzGHPySAyEU3jgvD8vEh1+a0gbHmFOk
+D8b7dR7i+BGoC9TAkMN6InyOhvDKltaT5iP7RM88high8pG/Ijc4/eiJMJaWgVM+
+2Fu02LeNf7YJS08lU5Kz+rl1XhN6VWGoufOd8hgA8um4OOMO8v8rBmsSfzGVm2L9
+BYeH7t3ktBHAtr5HguNpVnwqatx+q2qf2noKuo3OiGxRzxA0/vJpRNXwVaMcJadK
+ecAXjViZ+3Ksw5sRH6J3OfvdCaz6K43ED5kPWOp/GwKBgQDy0Y1tt6oY9NsDPx92
+g3fTwWqrNPqTYXKRAIncR/SL8cG1x+3mHFh5pPK+rIWykWig7l5k98jFn/86Y6Li
+7DxLPjmxVyBa4pkT1Jaz7rtZcsE8OOGFLl75zCvW1ygaAUOt9HmqeBRcYeqNuney
+dbjgO8W18nuL9619agygMdbJNwKBgQDmMggN+ClzKX4xWdUJ8j5rFqv+8MnOgsxH
+4xu9l2tdlpdquKqpcV/qh3WLasyK+lMOZ3BgskfBN/+RGAvCw+aZlTUiXHFsKujc
+zJOtD09Bu5fuF27CaSC7CoGmStspLrRpko+jfSkqEs7J1/Cem6QAyAo0liM2EI/I
+s4SWf7KuBwKBgA0WFhUvrM8jgxotsLhmZXLYHbOUa1y+B7qg2M0yY4+XO+VcHQol
+xO7pYNu92IbDJ2xk7FlssTlVYh/3drPcH3O+qsVP+MJtK+rRrj2MRDSR5rAkMKNI
+2H2F72bouZSNNOSPJG93gUFpVYbF4eWQSqJrFkC0DMyCUKtNp9iKVxUJAoGBAMcE
+2IvjU0raw9y9EwA/bRG/D0MiQQgHc8BvLOu0v0Gx0gWV1Q8cE1Y8eTbpRiCeHjLk
+4XbojDsURCPYy0o/ft6n7sFfdTyUuLE1OjQ0eUyWeNuDbOIua/rqMX9pVqP7WkWw
+TfmGW5GhoyFFTiaC379BM/mVGKpElVtrQaWwj/X/AoGBAO91lMkWQDIEYLMQ9qMX
+p8MLTNRdtd07RkBmP7aaBIScttSpm/wFBLADtJOClLS7dlip1FIliy4Ax6cKZiF0
+McTm7puNJJ6Ub/H16vDpsHzITHKL6YnxuYDTPdzmrnbWATfCwLg+djMGCPhVdzx8
+N16xDsaVNQD6rc7VbyPA9cG0
+-----END PRIVATE KEY-----
diff --git a/test/e2e-v2/cases/storage/opensearch/certs/root-ca.pem
b/test/e2e-v2/cases/storage/opensearch/certs/root-ca.pem
new file mode 100644
index 0000000000..2b7c4e8f19
--- /dev/null
+++ b/test/e2e-v2/cases/storage/opensearch/certs/root-ca.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDtTCCAp2gAwIBAgIUZGztPs0CHJ8E/LSMytSGotu5LgYwDQYJKoZIhvcNAQEL
+BQAwajELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ0wCwYDVQQHDARUZXN0MRMw
+EQYDVQQKDApTa3lXYWxraW5nMQ0wCwYDVQQLDARUZXN0MRswGQYDVQQDDBJTa3lX
+YWxraW5nIFJvb3QgQ0EwHhcNMjYwMTA0MTIyNjQ2WhcNMjgwMTA0MTIyNjQ2WjBq
+MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExDTALBgNVBAcMBFRlc3QxEzARBgNV
+BAoMClNreVdhbGtpbmcxDTALBgNVBAsMBFRlc3QxGzAZBgNVBAMMElNreVdhbGtp
+bmcgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANpXuaH+
+4p/B70uyC9bzfigFqL1g2Ps4YEzSKcpYdB87vw5TAz2uLoReBvE2chua88/JfpyJ
+QW6j35D5D7uaXIC9TI8aft6iYVrfZWI6O8geX2a0XhK9ZxnzJ9Il3MV//xSXU4/E
+ozwyIIyavoHoSpJQP4v8s0PID6P1IJy3Cg+CleWMKQXat9B6m7nW3DpF6WiRDd02
+hRATSAvcVqM6hmVt0YFfaHcSyKTaHxsghpnwozhq3iV2vWeuwtjvWdepMYCw7/b+
+WgXoJy/CCcapBh2TBHa7rLhTQH0rabhzuUnX6D9KJMGgNpKdeHAnE66NEAJ6Q8Yq
+xPynX6axpYrg4oECAwEAAaNTMFEwHQYDVR0OBBYEFIDowZ4aQHYrGf5sw8jIMOVw
+rjb3MB8GA1UdIwQYMBaAFIDowZ4aQHYrGf5sw8jIMOVwrjb3MA8GA1UdEwEB/wQF
+MAMBAf8wDQYJKoZIhvcNAQELBQADggEBADnjc1eZgpGpQznQaRrp603wxyOVGTuE
+eH54FP/WhSp0asHDEYUTwymMYjSGswCYZttJKS9FjqvLGRyCG8s5q33MwI5uVXR+
+9noGKpnbpEEVQwziO5/V60vzuCN6qZcjfNHKU5WnaHRGGJ0zONb1K8aeBq+WIAE0
+CylXC958O4ZGhrzz9W+fo74Q0nqJ2UVVvuq+wDKStO1KjfIYDDP3bOLa1gJgBdLr
+nCjJDyok2GgofnNUfIvb2uJvTm7C4R91bT6oq4OKG6N2+KFE1AIuNLFuAM+3hgBK
+HJir+GJHztoz0AtrzAikgmkfmqYj03A5fLl0NuWAD5+E3ON0nC9Yu+Y=
+-----END CERTIFICATE-----
diff --git a/test/e2e-v2/cases/storage/opensearch/certs/truststore.jks
b/test/e2e-v2/cases/storage/opensearch/certs/truststore.jks
new file mode 100644
index 0000000000..e646a2b781
Binary files /dev/null and
b/test/e2e-v2/cases/storage/opensearch/certs/truststore.jks differ
diff --git a/test/e2e-v2/cases/storage/opensearch/clientcert_config.yml
b/test/e2e-v2/cases/storage/opensearch/clientcert_config.yml
new file mode 100644
index 0000000000..bb11046b41
--- /dev/null
+++ b/test/e2e-v2/cases/storage/opensearch/clientcert_config.yml
@@ -0,0 +1,48 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+_meta:
+ type: "config"
+ config_version: 2
+
+config:
+ dynamic:
+ http:
+ anonymous_auth_enabled: false
+ xff:
+ enabled: false
+ authc:
+ clientcert_auth_domain:
+ description: "Authenticate via SSL client certificates"
+ http_enabled: true
+ transport_enabled: true
+ order: 0
+ http_authenticator:
+ type: clientcert
+ config:
+ username_attribute: cn
+ challenge: false
+ authentication_backend:
+ type: noop
+ basic_internal_auth_domain:
+ description: "Authenticate via HTTP Basic against internal users
database"
+ http_enabled: true
+ transport_enabled: true
+ order: 1
+ http_authenticator:
+ type: basic
+ challenge: true
+ authentication_backend:
+ type: intern
diff --git a/test/e2e-v2/cases/storage/opensearch/docker-compose.yml
b/test/e2e-v2/cases/storage/opensearch/docker-compose.yml
index 41b072b9e0..9a6876c63a 100644
--- a/test/e2e-v2/cases/storage/opensearch/docker-compose.yml
+++ b/test/e2e-v2/cases/storage/opensearch/docker-compose.yml
@@ -13,8 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-version: '2.1'
-
+version: "2.1"
services:
opensearch:
image: opensearchproject/opensearch:${OPENSEARCH_VERSION}
@@ -25,15 +24,81 @@ services:
environment:
- discovery.type=single-node
- cluster.routing.allocation.disk.threshold_enabled=false
- - plugins.security.ssl.http.enabled=false
+ # Security plugin settings
- DISABLE_INSTALL_DEMO_CONFIG=true
- - DISABLE_SECURITY_PLUGIN=true
+ - DISABLE_SECURITY_PLUGIN=false
+ - OPENSEARCH_INITIAL_ADMIN_PASSWORD=SecurePass@2024!
+ - NO_PROXY=opensearch,localhost,127.0.0.1
+ volumes:
+ - ./opensearch.yml:/usr/share/opensearch/config/opensearch.yml:ro
+ - ./certs:/usr/share/opensearch/config/certs:ro
+ - ./clientcert_config.yml:/tmp/clientcert_config.yml:ro
+ - ./internal_users.yml:/tmp/internal_users.yml:ro
healthcheck:
- test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9200"]
- interval: 5s
- timeout: 60s
- retries: 120
+ test:
+ - CMD
+ - bash
+ - -c
+ - |
+ curl --silent --fail \
+ --resolve opensearch:9200:127.0.0.1 \
+ --cacert /usr/share/opensearch/config/certs/root-ca.pem \
+ --cert /usr/share/opensearch/config/certs/admin.pem \
+ --key /usr/share/opensearch/config/certs/admin-key.pem \
+ https://opensearch:9200/_cluster/health || exit 1
+ interval: 10s
+ timeout: 10s
+ retries: 30
+ start_period: 60s
+ opensearch-init:
+ image: opensearchproject/opensearch:${OPENSEARCH_VERSION}
+ depends_on:
+ opensearch:
+ condition: service_healthy
+ networks:
+ - e2e
+ command: |
+ bash -c '
+ set -e
+ echo "Configuring client certificate authentication..."
+ /usr/share/opensearch/plugins/opensearch-security/tools/securityadmin.sh
\
+ -h opensearch \
+ -f /tmp/clientcert_config.yml \
+ -t config \
+ -icl \
+ -nhnv \
+ -cacert /usr/share/opensearch/config/certs/root-ca.pem \
+ -cert /usr/share/opensearch/config/certs/admin.pem \
+ -key /usr/share/opensearch/config/certs/admin-key.pem
+
+ echo "Configuring internal users..."
+ /usr/share/opensearch/plugins/opensearch-security/tools/securityadmin.sh
\
+ -h opensearch \
+ -f /tmp/internal_users.yml \
+ -t internalusers \
+ -icl \
+ -nhnv \
+ -cacert /usr/share/opensearch/config/certs/root-ca.pem \
+ -cert /usr/share/opensearch/config/certs/admin.pem \
+ -key /usr/share/opensearch/config/certs/admin-key.pem
+
+ sleep 2
+ echo "Adding certificate user to all_access role..."
+ export no_proxy=opensearch,localhost
+ curl --cacert /usr/share/opensearch/config/certs/root-ca.pem \
+ --cert /usr/share/opensearch/config/certs/admin.pem \
+ --key /usr/share/opensearch/config/certs/admin-key.pem \
+ -X PUT
"https://opensearch:9200/_plugins/_security/api/rolesmapping/all_access" \
+ -H "Content-Type: application/json" \
+ -d
"{\"users\":[\"node-0.example.com\"],\"backend_roles\":[\"admin\"],\"description\":\"Maps
admin to all_access\"}"
+ echo "✓ Client certificate authentication configured!"
+ '
+ volumes:
+ - ./clientcert_config.yml:/tmp/clientcert_config.yml:ro
+ - ./internal_users.yml:/tmp/internal_users.yml:ro
+ - ./certs:/usr/share/opensearch/config/certs:ro
+ restart: "no"
oap:
extends:
file: ../../../script/docker-compose/base-compose.yml
@@ -42,15 +107,23 @@ services:
SW_STORAGE: elasticsearch
SW_STORAGE_ES_CLUSTER_NODES: opensearch:9200
SW_ES_USER: admin
- SW_ES_PASSWORD: admin
+ SW_ES_PASSWORD: SecurePass@2024!
+ SW_STORAGE_ES_HTTP_PROTOCOL: https
+ SW_STORAGE_ES_SSL_JKS_PATH: /etc/skywalking/certs/truststore.jks
+ SW_STORAGE_ES_SSL_JKS_PASS: changeit
+ SW_STORAGE_ES_SSL_KEY_STORE_PATH: /etc/skywalking/certs/client.p12
+ SW_STORAGE_ES_SSL_KEY_STORE_PASS: changeit
ports:
- 12800
+ volumes:
+ - ./certs:/etc/skywalking/certs:ro
depends_on:
opensearch:
condition: service_healthy
+ opensearch-init:
+ condition: service_completed_successfully
networks:
- e2e
-
provider:
extends:
file: ../../../script/docker-compose/base-compose.yml
@@ -62,7 +135,6 @@ services:
condition: service_healthy
networks:
- e2e
-
consumer:
extends:
file: ../../../script/docker-compose/base-compose.yml
@@ -74,6 +146,5 @@ services:
condition: service_healthy
provider:
condition: service_healthy
-
networks:
e2e:
diff --git a/test/e2e-v2/cases/storage/opensearch/generate-certs.sh
b/test/e2e-v2/cases/storage/opensearch/generate-certs.sh
new file mode 100755
index 0000000000..a2344189a0
--- /dev/null
+++ b/test/e2e-v2/cases/storage/opensearch/generate-certs.sh
@@ -0,0 +1,88 @@
+#!/bin/bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -e
+
+CERTS_DIR="./certs"
+mkdir -p "$CERTS_DIR"
+
+# Generate Root CA
+openssl genrsa -out "$CERTS_DIR/root-ca-key.pem" 2048
+openssl req -new -x509 -sha256 -key "$CERTS_DIR/root-ca-key.pem" \
+ -subj "/C=US/ST=CA/L=Test/O=SkyWalking/OU=Test/CN=SkyWalking Root CA" \
+ -out "$CERTS_DIR/root-ca.pem" -days 730
+
+# Generate Node Certificate
+openssl genrsa -out "$CERTS_DIR/node-key.pem" 2048
+openssl req -new -key "$CERTS_DIR/node-key.pem" \
+ -subj "/C=US/ST=CA/L=Test/O=SkyWalking/OU=Test/CN=opensearch" \
+ -out "$CERTS_DIR/node.csr"
+
+# Create SAN config for node cert
+cat >"$CERTS_DIR/node-san.cnf" <<EOFSAN
+[req]
+distinguished_name = req_distinguished_name
+req_extensions = v3_req
+
+[req_distinguished_name]
+
+[v3_req]
+subjectAltName = @alt_names
+
+[alt_names]
+DNS.1 = opensearch
+DNS.2 = localhost
+IP.1 = 127.0.0.1
+EOFSAN
+
+openssl x509 -req -in "$CERTS_DIR/node.csr" \
+ -CA "$CERTS_DIR/root-ca.pem" -CAkey "$CERTS_DIR/root-ca-key.pem"
-CAcreateserial \
+ -out "$CERTS_DIR/node.pem" -days 730 -sha256 \
+ -extfile "$CERTS_DIR/node-san.cnf" -extensions v3_req
+
+# Generate Admin Certificate (for securityadmin tool)
+openssl genrsa -out "$CERTS_DIR/admin-key.pem" 2048
+openssl req -new -key "$CERTS_DIR/admin-key.pem" \
+ -subj "/C=US/ST=CA/L=Test/O=SkyWalking/OU=Test/CN=admin" \
+ -out "$CERTS_DIR/admin.csr"
+openssl x509 -req -in "$CERTS_DIR/admin.csr" \
+ -CA "$CERTS_DIR/root-ca.pem" -CAkey "$CERTS_DIR/root-ca-key.pem"
-CAcreateserial \
+ -out "$CERTS_DIR/admin.pem" -days 730 -sha256
+
+# Generate Client Certificate (for SkyWalking OAP)
+# CN must match the username in roles_mapping (node-0.example.com)
+openssl genrsa -out "$CERTS_DIR/client-key.pem" 2048
+openssl req -new -key "$CERTS_DIR/client-key.pem" \
+ -subj "/C=US/ST=CA/L=Test/O=SkyWalking/OU=Test/CN=node-0.example.com" \
+ -out "$CERTS_DIR/client.csr"
+openssl x509 -req -in "$CERTS_DIR/client.csr" \
+ -CA "$CERTS_DIR/root-ca.pem" -CAkey "$CERTS_DIR/root-ca-key.pem"
-CAcreateserial \
+ -out "$CERTS_DIR/client.pem" -days 730 -sha256
+
+# Create PKCS12 keystore for client (for Java applications)
+openssl pkcs12 -export -in "$CERTS_DIR/client.pem" -inkey
"$CERTS_DIR/client-key.pem" \
+ -out "$CERTS_DIR/client.p12" -name "node-0.example.com" -passout
pass:changeit
+
+# Create JKS truststore with root CA (remove existing if present)
+rm -f "$CERTS_DIR/truststore.jks"
+keytool -import -file "$CERTS_DIR/root-ca.pem" -alias root-ca \
+ -keystore "$CERTS_DIR/truststore.jks" -storepass changeit -noprompt
+
+# Clean up CSR and temp files
+rm -f "$CERTS_DIR"/*.csr "$CERTS_DIR"/*.srl "$CERTS_DIR/node-san.cnf"
+
+echo "✓ Certificates generated successfully in $CERTS_DIR"
+ls -lh "$CERTS_DIR"
diff --git a/test/e2e-v2/cases/storage/opensearch/internal_users.yml
b/test/e2e-v2/cases/storage/opensearch/internal_users.yml
new file mode 100644
index 0000000000..083636e06f
--- /dev/null
+++ b/test/e2e-v2/cases/storage/opensearch/internal_users.yml
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+_meta:
+ type: "internalusers"
+ config_version: 2
+
+admin:
+ hash: "$2y$12$u/p7/7wOrKU6ECfQ3/8RMeLjXJ6XvkoihKoi6oEKGfyxVW/LhEjh."
+ reserved: true
+ backend_roles:
+ - "admin"
+ description: "Admin user"
diff --git a/test/e2e-v2/cases/storage/opensearch/opensearch.yml
b/test/e2e-v2/cases/storage/opensearch/opensearch.yml
new file mode 100644
index 0000000000..44fa3ced55
--- /dev/null
+++ b/test/e2e-v2/cases/storage/opensearch/opensearch.yml
@@ -0,0 +1,36 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# OpenSearch SSL Configuration for E2E Tests
+
+network.host: 0.0.0.0
+
+plugins.security.ssl.http.enabled: true
+plugins.security.ssl.http.pemcert_filepath: certs/node.pem
+plugins.security.ssl.http.pemkey_filepath: certs/node-key.pem
+plugins.security.ssl.http.pemtrustedcas_filepath: certs/root-ca.pem
+plugins.security.ssl.http.clientauth_mode: OPTIONAL
+
+plugins.security.ssl.transport.enabled: true
+plugins.security.ssl.transport.pemcert_filepath: certs/node.pem
+plugins.security.ssl.transport.pemkey_filepath: certs/node-key.pem
+plugins.security.ssl.transport.pemtrustedcas_filepath: certs/root-ca.pem
+plugins.security.ssl.transport.enforce_hostname_verification: false
+
+# Admin DN for securityadmin tool
+plugins.security.authcz.admin_dn:
+ - "CN=admin,OU=Test,O=SkyWalking,L=Test,ST=CA,C=US"
+
+plugins.security.allow_default_init_securityindex: true
diff --git a/test/e2e-v2/java-test-service/pom.xml
b/test/e2e-v2/java-test-service/pom.xml
index 1c909770be..3c4ef358b8 100644
--- a/test/e2e-v2/java-test-service/pom.xml
+++ b/test/e2e-v2/java-test-service/pom.xml
@@ -53,11 +53,11 @@
<guava.version>30.1.1-jre</guava.version>
<h2.version>2.1.210</h2.version>
<mysql.version>8.0.13</mysql.version>
- <lombok.version>1.18.22</lombok.version>
+ <lombok.version>1.18.30</lombok.version>
<kafka-clients.version>2.4.1</kafka-clients.version>
<maven-failsafe-plugin.version>2.22.0</maven-failsafe-plugin.version>
- <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
+ <maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
<maven-checkstyle-plugin.version>3.1.0</maven-checkstyle-plugin.version>
</properties>