This is an automated email from the ASF dual-hosted git repository.

roryqi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new d1731cf8e1 [#12299] feat(server-common): CI-enforced tests for 
server-side HTTPS and mTLS (#12389)
d1731cf8e1 is described below

commit d1731cf8e1cdd1f2019fdac37fbe02f8a2edd4be
Author: Octavio Herrera Contreras <[email protected]>
AuthorDate: Tue Aug 11 21:01:02 2026 -0700

    [#12299] feat(server-common): CI-enforced tests for server-side HTTPS and 
mTLS (#12389)
    
    ### What changes were proposed in this pull request?
    - Added TestHttpsServerAuthentication test file to verify server side
    HTTPS across all 7 requested tests.
    - These changes introduce 6 new certificate fixtures for https server
    TLS/mTLS testing.
    - Includes new README.md file for instructions on certificate
    regeneration + regen script file.
    - Added TestTlsServerUtils to provide a reusable Jetty TLS server setup
    for future test modules
    - Updated server-common/build.gradle.kts to publish test classes and
    fixtures via test artifacts configuration
    
    Fix:  #12299
    
    ### Does this PR introduce _any_ user-facing change?
    No user facing changes
    
    ### How was this patch tested?
    Ran:
     ./gradlew rat
     Passed
    ./gradlew :server-common:test --tests
    "org.apache.gravitino.server.web.TestHttpsServerAuthentication"
     Passed
---
 server-common/build.gradle.kts                     |  13 +
 .../server/web/TestHttpsServerAuthentication.java  | 283 +++++++++++++++++++++
 .../gravitino/server/web/TestTlsServerUtils.java   |  72 ++++++
 server-common/src/test/resources/tls/README.md     | 166 ++++++++++++
 server-common/src/test/resources/tls/regenerate.sh | 210 +++++++++++++++
 .../test/resources/tls/test-client-truststore.p12  | Bin 0 -> 1350 bytes
 .../test/resources/tls/test-server-keystore.p12    | Bin 0 -> 2834 bytes
 .../test/resources/tls/test-server-truststore.p12  | Bin 0 -> 1366 bytes
 .../resources/tls/test-trusted-client-keystore.p12 | Bin 0 -> 2866 bytes
 .../tls/test-untrusted-client-keystore.p12         | Bin 0 -> 2870 bytes
 .../tls/test-untrusted-server-truststore.p12       | Bin 0 -> 1398 bytes
 11 files changed, 744 insertions(+)

diff --git a/server-common/build.gradle.kts b/server-common/build.gradle.kts
index fdcdf2f931..aa48f71060 100644
--- a/server-common/build.gradle.kts
+++ b/server-common/build.gradle.kts
@@ -72,3 +72,16 @@ tasks {
     environment("GRAVITINO_TEST", "true")
   }
 }
+
+val testJar by tasks.registering(Jar::class) {
+  archiveClassifier.set("tests")
+  from(sourceSets["test"].output)
+}
+
+configurations {
+  create("testArtifacts")
+}
+
+artifacts {
+  add("testArtifacts", testJar)
+}
diff --git 
a/server-common/src/test/java/org/apache/gravitino/server/web/TestHttpsServerAuthentication.java
 
b/server-common/src/test/java/org/apache/gravitino/server/web/TestHttpsServerAuthentication.java
new file mode 100644
index 0000000000..35cfbf1b82
--- /dev/null
+++ 
b/server-common/src/test/java/org/apache/gravitino/server/web/TestHttpsServerAuthentication.java
@@ -0,0 +1,283 @@
+/*
+ * 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.
+ */
+
+package org.apache.gravitino.server.web;
+
+import static 
org.apache.gravitino.server.web.TestTlsServerUtils.TEST_STORE_PASSWORD;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyStore;
+import java.time.Duration;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.TrustManagerFactory;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.rest.RESTUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class TestHttpsServerAuthentication {
+
+  private static final String TEST_STORE_TYPE = "PKCS12";
+  private static final char[] TEST_STORE_PASSWORD_CHARS = 
TEST_STORE_PASSWORD.toCharArray();
+
+  private static final String TEST_PATH = "/tls-test";
+  private static final String TEST_RESPONSE = "success";
+  private static final String SERVLET_RESPONSE = "success";
+
+  private JettyServer jettyServer;
+
+  @BeforeEach
+  public void setUp() {
+    jettyServer = new JettyServer();
+  }
+
+  @AfterEach
+  public void tearDown() throws Exception {
+    if (jettyServer != null) {
+      jettyServer.stop();
+    }
+  }
+
+  @Test
+  public void testClientAuthRejectsMissingTrustStore() throws Exception {
+    Config config = new Config(false) {};
+
+    config.set(JettyServerConfig.ENABLE_HTTPS, true);
+    config.set(JettyServerConfig.WEBSERVER_HTTPS_PORT, 
RESTUtils.findAvailablePort(6000, 7000));
+
+    config.set(
+        JettyServerConfig.SSL_KEYSTORE_PATH,
+        
TestTlsServerUtils.testResource("test-server-keystore.p12").toString());
+    config.set(JettyServerConfig.SSL_KEYSTORE_PASSWORD, TEST_STORE_PASSWORD);
+    config.set(JettyServerConfig.SSL_MANAGER_PASSWORD, TEST_STORE_PASSWORD);
+    config.set(JettyServerConfig.ENABLE_CLIENT_AUTH, true);
+
+    assertThrows(IllegalArgumentException.class, () -> 
JettyServerConfig.fromConfig(config));
+  }
+
+  @Test
+  public void testMutualTlsAcceptsTrustedClientCertificate() throws Exception {
+    int port =
+        TestTlsServerUtils.startHttpsServer(jettyServer, true, 
createTestServlet(), TEST_PATH);
+
+    HttpClient client =
+        createHttpsClient(
+            TestTlsServerUtils.testResource("test-client-truststore.p12"),
+            
TestTlsServerUtils.testResource("test-trusted-client-keystore.p12"));
+
+    HttpRequest request = createHttpsRequest(port);
+
+    HttpResponse<String> response = client.send(request, 
HttpResponse.BodyHandlers.ofString());
+
+    assertEquals(HttpServletResponse.SC_OK, response.statusCode());
+    assertEquals(TEST_RESPONSE, response.body());
+  }
+
+  @Test
+  public void testMutualTlsRejectsMissingClientCertificate() throws Exception {
+    int port =
+        TestTlsServerUtils.startHttpsServer(jettyServer, true, 
createTestServlet(), TEST_PATH);
+
+    HttpClient client =
+        
createHttpsClient(TestTlsServerUtils.testResource("test-client-truststore.p12"),
 null);
+
+    HttpRequest request = createHttpsRequest(port);
+
+    Exception exception =
+        assertThrows(
+            Exception.class, () -> client.send(request, 
HttpResponse.BodyHandlers.ofString()));
+
+    assertHandshakeFailure(exception);
+  }
+
+  @Test
+  public void testMutualTlsRejectsUntrustedClientCertificate() throws 
Exception {
+    int port =
+        TestTlsServerUtils.startHttpsServer(jettyServer, true, 
createTestServlet(), TEST_PATH);
+
+    HttpClient client =
+        createHttpsClient(
+            TestTlsServerUtils.testResource("test-client-truststore.p12"),
+            
TestTlsServerUtils.testResource("test-untrusted-client-keystore.p12"));
+
+    HttpRequest request = createHttpsRequest(port);
+
+    Exception exception =
+        assertThrows(
+            Exception.class, () -> client.send(request, 
HttpResponse.BodyHandlers.ofString()));
+
+    assertHandshakeFailure(exception);
+  }
+
+  @Test
+  public void testClientRejectsUntrustedServerCertificate() throws Exception {
+    int port =
+        TestTlsServerUtils.startHttpsServer(jettyServer, false, 
createTestServlet(), TEST_PATH);
+
+    HttpClient client =
+        createHttpsClient(
+            
TestTlsServerUtils.testResource("test-untrusted-server-truststore.p12"), null);
+
+    HttpRequest request = createHttpsRequest(port);
+
+    Exception exception =
+        assertThrows(
+            Exception.class, () -> client.send(request, 
HttpResponse.BodyHandlers.ofString()));
+
+    assertHandshakeFailure(exception);
+  }
+
+  @Test
+  public void testHttpsWithoutClientAuthentication() throws Exception {
+    int port =
+        TestTlsServerUtils.startHttpsServer(jettyServer, false, 
createTestServlet(), TEST_PATH);
+
+    HttpClient client =
+        
createHttpsClient(TestTlsServerUtils.testResource("test-client-truststore.p12"),
 null);
+
+    HttpRequest request = createHttpsRequest(port);
+
+    HttpResponse<String> response = client.send(request, 
HttpResponse.BodyHandlers.ofString());
+
+    assertEquals(HttpServletResponse.SC_OK, response.statusCode());
+    assertEquals(TEST_RESPONSE, response.body());
+  }
+
+  @Test
+  public void testHttpWithoutCustomTlsConfiguration() throws Exception {
+    int port = startHttpServer(jettyServer, createTestServlet(), TEST_PATH);
+
+    HttpRequest request =
+        HttpRequest.newBuilder()
+            .uri(URI.create("http://localhost:"; + port + TEST_PATH))
+            .timeout(Duration.ofSeconds(10))
+            .GET()
+            .build();
+
+    HttpResponse<String> response =
+        HttpClient.newHttpClient().send(request, 
HttpResponse.BodyHandlers.ofString());
+
+    assertEquals(HttpServletResponse.SC_OK, response.statusCode());
+    assertEquals(TEST_RESPONSE, response.body());
+  }
+
+  public static int startHttpServer(JettyServer server, HttpServlet servlet, 
String servletPath)
+      throws Exception {
+
+    int port = RESTUtils.findAvailablePort(5000, 6000);
+
+    Config config = new Config(false) {};
+    config.set(JettyServerConfig.WEBSERVER_HTTP_PORT, port);
+
+    server.initialize(JettyServerConfig.fromConfig(config), "test", false);
+    server.start();
+    server.addServlet(servlet, servletPath);
+
+    return port;
+  }
+
+  private static HttpClient createHttpsClient(Path trustStorePath, Path 
clientKeyStorePath)
+      throws Exception {
+    KeyStore trustStore = loadStore(trustStorePath);
+
+    TrustManagerFactory trustManagerFactory =
+        
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+    trustManagerFactory.init(trustStore);
+
+    SSLContext sslContext = SSLContext.getInstance("TLS");
+
+    if (clientKeyStorePath == null) {
+      sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
+    } else {
+      KeyStore clientKeyStore = loadStore(clientKeyStorePath);
+
+      KeyManagerFactory keyManagerFactory =
+          
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+      keyManagerFactory.init(clientKeyStore, TEST_STORE_PASSWORD_CHARS);
+
+      sslContext.init(
+          keyManagerFactory.getKeyManagers(), 
trustManagerFactory.getTrustManagers(), null);
+    }
+
+    return HttpClient.newBuilder()
+        .sslContext(sslContext)
+        .connectTimeout(Duration.ofSeconds(10))
+        .build();
+  }
+
+  private static HttpRequest createHttpsRequest(int port) {
+    return HttpRequest.newBuilder()
+        .uri(URI.create("https://localhost:"; + port + TEST_PATH))
+        .timeout(Duration.ofSeconds(10))
+        .GET()
+        .build();
+  }
+
+  private static KeyStore loadStore(Path path) throws Exception {
+    KeyStore keyStore = KeyStore.getInstance(TEST_STORE_TYPE);
+
+    try (InputStream inputStream = Files.newInputStream(path)) {
+      keyStore.load(inputStream, TEST_STORE_PASSWORD_CHARS);
+    }
+
+    return keyStore;
+  }
+
+  private static HttpServlet createTestServlet() {
+    return new HttpServlet() {
+      @Override
+      protected void doGet(HttpServletRequest request, HttpServletResponse 
response)
+          throws IOException {
+        response.setStatus(HttpServletResponse.SC_OK);
+        response.setContentType("text/plain");
+        response.getWriter().write(SERVLET_RESPONSE);
+      }
+    };
+  }
+
+  private static void assertHandshakeFailure(Throwable throwable) {
+    Throwable current = throwable;
+
+    while (current != null) {
+      if (current instanceof SSLHandshakeException) {
+        return;
+      }
+
+      current = current.getCause();
+    }
+
+    fail("Expected an SSL handshake failure, but received: " + throwable, 
throwable);
+  }
+}
diff --git 
a/server-common/src/test/java/org/apache/gravitino/server/web/TestTlsServerUtils.java
 
b/server-common/src/test/java/org/apache/gravitino/server/web/TestTlsServerUtils.java
new file mode 100644
index 0000000000..83e3b48b50
--- /dev/null
+++ 
b/server-common/src/test/java/org/apache/gravitino/server/web/TestTlsServerUtils.java
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+package org.apache.gravitino.server.web;
+
+import java.nio.file.Path;
+import java.util.Objects;
+import javax.servlet.http.HttpServlet;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.rest.RESTUtils;
+
+public final class TestTlsServerUtils {
+
+  public static final String TEST_STORE_PASSWORD = "changeit";
+
+  private TestTlsServerUtils() {}
+
+  public static int startHttpsServer(
+      JettyServer server, boolean clientAuth, HttpServlet servlet, String 
servletPath)
+      throws Exception {
+
+    int port = RESTUtils.findAvailablePort(6000, 7000);
+
+    Config config = new Config(false) {};
+    config.set(JettyServerConfig.ENABLE_HTTPS, true);
+    config.set(JettyServerConfig.WEBSERVER_HTTPS_PORT, port);
+
+    config.set(
+        JettyServerConfig.SSL_KEYSTORE_PATH, 
testResource("test-server-keystore.p12").toString());
+    config.set(JettyServerConfig.SSL_KEYSTORE_PASSWORD, TEST_STORE_PASSWORD);
+    config.set(JettyServerConfig.SSL_MANAGER_PASSWORD, TEST_STORE_PASSWORD);
+
+    config.set(JettyServerConfig.ENABLE_CLIENT_AUTH, clientAuth);
+
+    if (clientAuth) {
+      config.set(
+          JettyServerConfig.SSL_TRUST_STORE_PATH,
+          testResource("test-server-truststore.p12").toString());
+      config.set(JettyServerConfig.SSL_TRUST_STORE_PASSWORD, 
TEST_STORE_PASSWORD);
+    }
+
+    server.initialize(JettyServerConfig.fromConfig(config), "test", false);
+    server.start();
+    server.addServlet(servlet, servletPath);
+
+    return port;
+  }
+
+  public static Path testResource(String filename) throws Exception {
+    return Path.of(
+        Objects.requireNonNull(
+                TestTlsServerUtils.class.getResource("/tls/" + filename),
+                "Missing TLS test resource: " + filename)
+            .toURI());
+  }
+}
diff --git a/server-common/src/test/resources/tls/README.md 
b/server-common/src/test/resources/tls/README.md
new file mode 100644
index 0000000000..6f468ac067
--- /dev/null
+++ b/server-common/src/test/resources/tls/README.md
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+
+# TLS Integration Test Fixtures
+
+This directory contains static PKCS12 keystore and truststore fixtures used by 
the TLS integration tests.
+
+These files are committed to the repository so the tests can perform real TLS 
and mutual TLS handshakes.
+
+## Test-only warning
+
+All certificates, private keys, aliases, and passwords in this directory are 
for testing only.
+
+They are intentionally stored in the repository and must not be used in 
production or for any security-sensitive purpose.
+
+## Passwords
+
+All PKCS12 stores use the following password:
+
+```text
+changeit
+```
+
+Private-key entries also use:
+
+```text
+changeit
+```
+
+The same password is used for all fixtures to keep the integration-test 
configuration simple.
+
+## Fixture files
+
+| Filename                               | Purpose                             
                                                                                
            | Alias                   | Certificate subject                     
                                                        |
+| -------------------------------------- | 
-------------------------------------------------------------------------------------------------------------------------------
 | ----------------------- | 
-----------------------------------------------------------------------------------------------
 |
+| `test-server-keystore.p12`             | Contains the private key and 
certificate presented by the Jetty HTTPS server.                                
                   | `test-server`           | `CN=localhost, OU=TLS 
Integration Tests, O=Apache Gravitino, L=Test, ST=Test, C=US`             |
+| `test-server-truststore.p12`           | Contains the trusted client 
certificate used by the server during mutual TLS authentication.                
                    | `test-trusted-client`   | `CN=test-trusted-client, OU=TLS 
Integration Tests, O=Apache Gravitino, L=Test, ST=Test, C=US`   |
+| `test-client-truststore.p12`           | Contains the server certificate 
trusted by the HTTPS client.                                                    
                | `test-server`           | `CN=localhost, OU=TLS Integration 
Tests, O=Apache Gravitino, L=Test, ST=Test, C=US`             |
+| `test-trusted-client-keystore.p12`     | Contains the private key and 
certificate for the client that should be accepted by the server during mutual 
TLS authentication. | `test-trusted-client`   | `CN=test-trusted-client, OU=TLS 
Integration Tests, O=Apache Gravitino, L=Test, ST=Test, C=US`   |
+| `test-untrusted-client-keystore.p12`   | Contains a client private key and 
certificate that is not trusted by the server and should cause the TLS 
handshake to fail.     | `test-untrusted-client` | `CN=test-untrusted-client, 
OU=TLS Integration Tests, O=Apache Gravitino, L=Test, ST=Test, C=US` |
+| `test-untrusted-server-truststore.p12` | Contains an unrelated trusted 
certificate, causing the client to reject the test server certificate.          
                  | `test-unrelated-server` | `CN=test-unrelated-server, OU=TLS 
Integration Tests, O=Apache Gravitino, L=Test, ST=Test, C=US` |
+
+## Server certificate hostname
+
+The test server is accessed using:
+
+```text
+https://localhost:<port>
+```
+
+The server certificate must therefore contain the following Subject 
Alternative Name:
+
+```text
+DNS:localhost
+```
+
+## Expected entry types
+
+Identity keystores should contain a private-key entry:
+
+```text
+Entry type: PrivateKeyEntry
+```
+
+This applies to:
+
+* `test-server-keystore.p12`
+* `test-trusted-client-keystore.p12`
+* `test-untrusted-client-keystore.p12`
+
+Truststores should contain trusted certificate entries:
+
+```text
+Entry type: trustedCertEntry
+```
+
+This applies to:
+
+* `test-server-truststore.p12`
+* `test-client-truststore.p12`
+* `test-untrusted-server-truststore.p12`
+
+## Inspecting a fixture
+
+Use the following command to inspect a PKCS12 fixture:
+
+```bash
+keytool -list -v \
+  -storetype PKCS12 \
+  -keystore test-server-keystore.p12 \
+  -storepass changeit
+```
+
+Replace the filename as needed.
+
+The output should be used to verify:
+
+* alias name
+* entry type
+* certificate subject
+* certificate issuer
+* certificate validity dates
+* Subject Alternative Names
+* certificate chain
+
+## Regeneration requirements
+
+When regenerating the fixtures, preserve the following requirements:
+
+1. All stores must use the PKCS12 format.
+2. All store passwords must be `changeit`.
+3. All private-key passwords must be `changeit`.
+4. The server certificate must include `DNS:localhost` as a Subject 
Alternative Name.
+5. The trusted client certificate must be present in the server truststore.
+6. The server certificate must be present in the client truststore.
+7. The untrusted client certificate must not be present in the server 
truststore.
+8. The untrusted server truststore must not contain the test server 
certificate or any certificate authority that issued it.
+9. Trusted and untrusted identities must use different private keys and 
certificates.
+10. The filenames, aliases, and certificate subjects documented above must be 
updated if they change.
+
+## Regeneration instructions
+
+The fixtures can be regenerated using the JDK `keytool` utility.
+
+Run the fixture-generation script from this directory:
+
+```bash
+./regenerate.sh
+```
+
+The script recreates all PKCS12 keystores and truststores using the passwords, 
aliases, subjects, and trust relationships documented above.
+
+After regeneration, inspect each fixture using:
+
+```bash
+keytool -list -v \
+  -storetype PKCS12 \
+  -keystore <fixture-name>.p12 \
+  -storepass changeit
+```
+
+The generated certificates are valid for 3,650 days (10 years) and must be 
regenerated before they expire.
+
+## Running the integration test
+
+From the repository root, run:
+
+```bash
+./gradlew :server-common:test --tests 
"org.apache.gravitino.server.web.TestJettyServer"
+```
\ No newline at end of file
diff --git a/server-common/src/test/resources/tls/regenerate.sh 
b/server-common/src/test/resources/tls/regenerate.sh
new file mode 100755
index 0000000000..8369dafc90
--- /dev/null
+++ b/server-common/src/test/resources/tls/regenerate.sh
@@ -0,0 +1,210 @@
+#!/usr/bin/env 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 -euo pipefail
+
+STORE_PASSWORD="changeit"
+KEY_PASSWORD="changeit"
+STORE_TYPE="PKCS12"
+VALIDITY_DAYS="3650"
+KEY_ALGORITHM="RSA"
+KEY_SIZE="2048"
+SIGNATURE_ALGORITHM="SHA256withRSA"
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+cd "${SCRIPT_DIR}"
+
+SERVER_KEYSTORE="test-server-keystore.p12"
+SERVER_TRUSTSTORE="test-server-truststore.p12"
+CLIENT_TRUSTSTORE="test-client-truststore.p12"
+TRUSTED_CLIENT_KEYSTORE="test-trusted-client-keystore.p12"
+UNTRUSTED_CLIENT_KEYSTORE="test-untrusted-client-keystore.p12"
+UNTRUSTED_SERVER_TRUSTSTORE="test-untrusted-server-truststore.p12"
+
+SERVER_ALIAS="test-server"
+TRUSTED_CLIENT_ALIAS="test-trusted-client"
+UNTRUSTED_CLIENT_ALIAS="test-untrusted-client"
+UNRELATED_SERVER_ALIAS="test-unrelated-server"
+
+SERVER_SUBJECT="CN=localhost,OU=TLS Integration Tests,O=Apache 
Gravitino,L=Test,ST=Test,C=US"
+TRUSTED_CLIENT_SUBJECT="CN=test-trusted-client,OU=TLS Integration 
Tests,O=Apache Gravitino,L=Test,ST=Test,C=US"
+UNTRUSTED_CLIENT_SUBJECT="CN=test-untrusted-client,OU=TLS Integration 
Tests,O=Apache Gravitino,L=Test,ST=Test,C=US"
+UNRELATED_SERVER_SUBJECT="CN=test-unrelated-server,OU=TLS Integration 
Tests,O=Apache Gravitino,L=Test,ST=Test,C=US"
+
+TEMP_DIR="$(mktemp -d)"
+
+cleanup() {
+  rm -rf "${TEMP_DIR}"
+}
+trap cleanup EXIT
+
+if ! command -v keytool >/dev/null 2>&1; then
+  echo "Error: keytool was not found on PATH." >&2
+  echo "Install a JDK or add its bin directory to PATH." >&2
+  exit 1
+fi
+
+echo "Removing existing TLS test fixtures..."
+
+rm -f \
+  "${SERVER_KEYSTORE}" \
+  "${SERVER_TRUSTSTORE}" \
+  "${CLIENT_TRUSTSTORE}" \
+  "${TRUSTED_CLIENT_KEYSTORE}" \
+  "${UNTRUSTED_CLIENT_KEYSTORE}" \
+  "${UNTRUSTED_SERVER_TRUSTSTORE}"
+
+echo "Generating server identity..."
+
+keytool -genkeypair \
+  -alias "${SERVER_ALIAS}" \
+  -dname "${SERVER_SUBJECT}" \
+  -ext "SAN=dns:localhost" \
+  -ext "EKU=serverAuth" \
+  -keyalg "${KEY_ALGORITHM}" \
+  -keysize "${KEY_SIZE}" \
+  -sigalg "${SIGNATURE_ALGORITHM}" \
+  -validity "${VALIDITY_DAYS}" \
+  -storetype "${STORE_TYPE}" \
+  -keystore "${SERVER_KEYSTORE}" \
+  -storepass "${STORE_PASSWORD}" \
+  -keypass "${KEY_PASSWORD}" \
+  -noprompt
+
+echo "Generating trusted client identity..."
+
+keytool -genkeypair \
+  -alias "${TRUSTED_CLIENT_ALIAS}" \
+  -dname "${TRUSTED_CLIENT_SUBJECT}" \
+  -ext "EKU=clientAuth" \
+  -keyalg "${KEY_ALGORITHM}" \
+  -keysize "${KEY_SIZE}" \
+  -sigalg "${SIGNATURE_ALGORITHM}" \
+  -validity "${VALIDITY_DAYS}" \
+  -storetype "${STORE_TYPE}" \
+  -keystore "${TRUSTED_CLIENT_KEYSTORE}" \
+  -storepass "${STORE_PASSWORD}" \
+  -keypass "${KEY_PASSWORD}" \
+  -noprompt
+
+echo "Generating untrusted client identity..."
+
+keytool -genkeypair \
+  -alias "${UNTRUSTED_CLIENT_ALIAS}" \
+  -dname "${UNTRUSTED_CLIENT_SUBJECT}" \
+  -ext "EKU=clientAuth" \
+  -keyalg "${KEY_ALGORITHM}" \
+  -keysize "${KEY_SIZE}" \
+  -sigalg "${SIGNATURE_ALGORITHM}" \
+  -validity "${VALIDITY_DAYS}" \
+  -storetype "${STORE_TYPE}" \
+  -keystore "${UNTRUSTED_CLIENT_KEYSTORE}" \
+  -storepass "${STORE_PASSWORD}" \
+  -keypass "${KEY_PASSWORD}" \
+  -noprompt
+
+echo "Generating unrelated server certificate..."
+
+keytool -genkeypair \
+  -alias "${UNRELATED_SERVER_ALIAS}" \
+  -dname "${UNRELATED_SERVER_SUBJECT}" \
+  -ext "SAN=dns:unrelated.invalid" \
+  -ext "EKU=serverAuth" \
+  -keyalg "${KEY_ALGORITHM}" \
+  -keysize "${KEY_SIZE}" \
+  -sigalg "${SIGNATURE_ALGORITHM}" \
+  -validity "${VALIDITY_DAYS}" \
+  -storetype "${STORE_TYPE}" \
+  -keystore "${TEMP_DIR}/unrelated-server-keystore.p12" \
+  -storepass "${STORE_PASSWORD}" \
+  -keypass "${KEY_PASSWORD}" \
+  -noprompt
+
+echo "Exporting certificates..."
+
+keytool -exportcert \
+  -alias "${SERVER_ALIAS}" \
+  -keystore "${SERVER_KEYSTORE}" \
+  -storetype "${STORE_TYPE}" \
+  -storepass "${STORE_PASSWORD}" \
+  -file "${TEMP_DIR}/server.crt" \
+  -rfc
+
+keytool -exportcert \
+  -alias "${TRUSTED_CLIENT_ALIAS}" \
+  -keystore "${TRUSTED_CLIENT_KEYSTORE}" \
+  -storetype "${STORE_TYPE}" \
+  -storepass "${STORE_PASSWORD}" \
+  -file "${TEMP_DIR}/trusted-client.crt" \
+  -rfc
+
+keytool -exportcert \
+  -alias "${UNRELATED_SERVER_ALIAS}" \
+  -keystore "${TEMP_DIR}/unrelated-server-keystore.p12" \
+  -storetype "${STORE_TYPE}" \
+  -storepass "${STORE_PASSWORD}" \
+  -file "${TEMP_DIR}/unrelated-server.crt" \
+  -rfc
+
+echo "Creating server truststore..."
+
+keytool -importcert \
+  -alias "${TRUSTED_CLIENT_ALIAS}" \
+  -file "${TEMP_DIR}/trusted-client.crt" \
+  -keystore "${SERVER_TRUSTSTORE}" \
+  -storetype "${STORE_TYPE}" \
+  -storepass "${STORE_PASSWORD}" \
+  -noprompt
+
+echo "Creating client truststore..."
+
+keytool -importcert \
+  -alias "${SERVER_ALIAS}" \
+  -file "${TEMP_DIR}/server.crt" \
+  -keystore "${CLIENT_TRUSTSTORE}" \
+  -storetype "${STORE_TYPE}" \
+  -storepass "${STORE_PASSWORD}" \
+  -noprompt
+
+echo "Creating untrusted server truststore..."
+
+keytool -importcert \
+  -alias "${UNRELATED_SERVER_ALIAS}" \
+  -file "${TEMP_DIR}/unrelated-server.crt" \
+  -keystore "${UNTRUSTED_SERVER_TRUSTSTORE}" \
+  -storetype "${STORE_TYPE}" \
+  -storepass "${STORE_PASSWORD}" \
+  -noprompt
+
+echo
+echo "TLS integration-test fixtures regenerated successfully."
+echo
+echo "Generated files:"
+printf '  %s\n' \
+  "${SERVER_KEYSTORE}" \
+  "${SERVER_TRUSTSTORE}" \
+  "${CLIENT_TRUSTSTORE}" \
+  "${TRUSTED_CLIENT_KEYSTORE}" \
+  "${UNTRUSTED_CLIENT_KEYSTORE}" \
+  "${UNTRUSTED_SERVER_TRUSTSTORE}"
+
+echo
+echo "All store and private-key passwords: ${STORE_PASSWORD}"
+echo "These fixtures are for testing only and must not be used in production."
\ No newline at end of file
diff --git a/server-common/src/test/resources/tls/test-client-truststore.p12 
b/server-common/src/test/resources/tls/test-client-truststore.p12
new file mode 100644
index 0000000000..abae73c911
Binary files /dev/null and 
b/server-common/src/test/resources/tls/test-client-truststore.p12 differ
diff --git a/server-common/src/test/resources/tls/test-server-keystore.p12 
b/server-common/src/test/resources/tls/test-server-keystore.p12
new file mode 100644
index 0000000000..08c61ab36d
Binary files /dev/null and 
b/server-common/src/test/resources/tls/test-server-keystore.p12 differ
diff --git a/server-common/src/test/resources/tls/test-server-truststore.p12 
b/server-common/src/test/resources/tls/test-server-truststore.p12
new file mode 100644
index 0000000000..2bacf50a46
Binary files /dev/null and 
b/server-common/src/test/resources/tls/test-server-truststore.p12 differ
diff --git 
a/server-common/src/test/resources/tls/test-trusted-client-keystore.p12 
b/server-common/src/test/resources/tls/test-trusted-client-keystore.p12
new file mode 100644
index 0000000000..e6ef643640
Binary files /dev/null and 
b/server-common/src/test/resources/tls/test-trusted-client-keystore.p12 differ
diff --git 
a/server-common/src/test/resources/tls/test-untrusted-client-keystore.p12 
b/server-common/src/test/resources/tls/test-untrusted-client-keystore.p12
new file mode 100644
index 0000000000..2105f7027a
Binary files /dev/null and 
b/server-common/src/test/resources/tls/test-untrusted-client-keystore.p12 differ
diff --git 
a/server-common/src/test/resources/tls/test-untrusted-server-truststore.p12 
b/server-common/src/test/resources/tls/test-untrusted-server-truststore.p12
new file mode 100644
index 0000000000..2677d26c49
Binary files /dev/null and 
b/server-common/src/test/resources/tls/test-untrusted-server-truststore.p12 
differ

Reply via email to