diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/PrivateKeyStore.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/PrivateKeyStore.java
index bb2d8d201b1..8dddd90a8cf 100644
--- a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/PrivateKeyStore.java
+++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/PrivateKeyStore.java
@@ -68,6 +68,7 @@ default String getApplicationSecret(String appName, String keyName) {
/**
* Retrieve the application secret based on the configured key name as char[].
+ * @deprecated
* The application name specifies what component is this secret for;
* for example, jdbc for accessing the secret for the jdbc user.
* The default implementation assumes the key name is the secret.
@@ -75,8 +76,23 @@ default String getApplicationSecret(String appName, String keyName) {
* @param keyName configured value for the secret
* @return secret for the given key and application as char[]
*/
+ @Deprecated
default char[] getSecret(String appName, String keyName) {
final String secret = getApplicationSecret(appName, keyName);
return secret != null ? secret.toCharArray() : null;
}
+
+ /**
+ * Retrieve the application secret based on the configured key name as char[].
+ * The application name specifies what component is this secret for;
+ * for example, jdbc for accessing the secret for the jdbc user.
+ * The default implementation assumes the key name is the secret.
+ * @param appName application name for the secret
+ * @param keygroupName key group name for the secret
+ * @param keyName name of the secret
+ * @return secret for the given key and application as char[]
+ */
+ default char[] getSecret(String appName, String keygroupName, String keyName) {
+ return keyName.toCharArray();
+ }
}
diff --git a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/AWSParameterStorePrivateKeyStore.java b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/AWSParameterStorePrivateKeyStore.java
index b04ebdbd2c7..6d0a7a8786c 100644
--- a/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/AWSParameterStorePrivateKeyStore.java
+++ b/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/impl/AWSParameterStorePrivateKeyStore.java
@@ -50,7 +50,7 @@ public class AWSParameterStorePrivateKeyStore implements PrivateKeyStore {
}
@Override
- public char[] getSecret(String appName, String keyName) {
+ public char[] getSecret(String appName, String keygroupName, String keyName) {
return getSsmParameter(keyName).toCharArray();
}
diff --git a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/AWSParameterStorePrivateKeyStoreTest.java b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/AWSParameterStorePrivateKeyStoreTest.java
index 16a451b6066..089a565e8e2 100644
--- a/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/AWSParameterStorePrivateKeyStoreTest.java
+++ b/libs/java/auth_core/src/test/java/com/yahoo/athenz/auth/impl/AWSParameterStorePrivateKeyStoreTest.java
@@ -49,7 +49,7 @@ public void testGetSecret() {
when(ssmClient.getParameter(any(Consumer.class)))
.thenReturn(GetParameterResponse.builder().parameter(Parameter.builder().value("secret").build()).build());
AWSParameterStorePrivateKeyStore store = (AWSParameterStorePrivateKeyStore)getFactory(ssmClient).create();
- assertEquals(store.getSecret("app1", "key1"), "secret".toCharArray());
+ assertEquals(store.getSecret("app1", null, "key1"), "secret".toCharArray());
}
@Test
diff --git a/libs/java/server_k8s_common/README.md b/libs/java/server_k8s_common/README.md
new file mode 100644
index 00000000000..4db0efd2330
--- /dev/null
+++ b/libs/java/server_k8s_common/README.md
@@ -0,0 +1,12 @@
+Athenz Server Common Classes
+============================
+
+Common classes used throughout Athenz Server components if server is deployed in Kubernetes.
+
+- KeyStore: PrivateKeyStore implementation using Kubernetes secrets
+
+## License
+
+Copyright The Athenz Authors
+
+Licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
diff --git a/libs/java/server_k8s_common/pom.xml b/libs/java/server_k8s_common/pom.xml
new file mode 100644
index 00000000000..d990792ec03
--- /dev/null
+++ b/libs/java/server_k8s_common/pom.xml
@@ -0,0 +1,63 @@
+
+
+
+ 4.0.0
+
+
+ com.yahoo.athenz
+ athenz
+ 1.11.60-SNAPSHOT
+ ../../../pom.xml
+
+
+ athenz-server-k8s-common
+ athenz-k8s-server-common
+ Athenz Kubernetes Server Common Packages
+ jar
+
+
+ 1.00
+
+
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.server.version}
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.server.version}
+ test
+
+
+ com.squareup.okhttp3
+ mockwebserver
+ ${okhttp3.mockwebserver.version}
+ test
+
+
+ com.yahoo.athenz
+ athenz-auth-core
+ ${project.parent.version}
+
+
+ io.kubernetes
+ client-java
+ ${kubernetes-client.version}
+
+
+
+
diff --git a/libs/java/server_k8s_common/src/main/java/io/athenz/server/k8s/common/impl/KubernetesSecretPrivateKeyStore.java b/libs/java/server_k8s_common/src/main/java/io/athenz/server/k8s/common/impl/KubernetesSecretPrivateKeyStore.java
new file mode 100644
index 00000000000..a6d317ba8d2
--- /dev/null
+++ b/libs/java/server_k8s_common/src/main/java/io/athenz/server/k8s/common/impl/KubernetesSecretPrivateKeyStore.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * Licensed 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 io.athenz.server.k8s.common.impl;
+
+import com.yahoo.athenz.auth.PrivateKeyStore;
+import com.yahoo.athenz.auth.ServerPrivateKey;
+import com.yahoo.athenz.auth.util.Crypto;
+import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.openapi.Configuration;
+import io.kubernetes.client.openapi.apis.CoreV1Api;
+import io.kubernetes.client.openapi.models.V1Secret;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.invoke.MethodHandles;
+import java.nio.charset.StandardCharsets;
+import java.security.PrivateKey;
+
+public class KubernetesSecretPrivateKeyStore implements PrivateKeyStore {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private static final String ZMS_SERVICE = "zms";
+ private static final String ZTS_SERVICE = "zts";
+ private static final String MSD_SERVICE = "msd";
+
+ private static final String ATHENZ_PROP_K8S_ZMS_KEY_NAME = "athenz.k8s.zms.key_name";
+ private static final String ATHENZ_PROP_K8S_ZMS_KEY_ID_NAME = "athenz.k8s.zms.key_id_name";
+ private static final String ATHENZ_PROP_K8S_ZTS_KEY_NAME = "athenz.k8s.zts.key_name";
+ private static final String ATHENZ_PROP_K8S_ZTS_KEY_ID_NAME = "athenz.k8s.zts.key_id_name";
+ private static final String ATHENZ_PROP_K8S_MSD_KEY_NAME = "athenz.k8s.msd.key_name";
+ private static final String ATHENZ_PROP_K8S_MSD_KEY_ID_NAME = "athenz.k8s.msd.key_id_name";
+
+ private static final String ATHENZ_K8S_DEFAULT_KEY_NAME = "service_k8s_private_key";
+ private static final String ATHENZ_K8S_DEFAULT_KEY_ID_NAME = "service_k8s_private_key_id";
+
+ private final ApiClient k8sClient;
+
+ private static final String ATHENZ_K8S_CONNECT_TIMEOUT = "athenz.k8s.connect_timeout";
+ private static final String ATHENZ_K8S_READ_TIMEOUT = "athenz.k8s.read_timeout";
+
+ public KubernetesSecretPrivateKeyStore(ApiClient k8sClient) {
+ this.k8sClient = k8sClient;
+ this.k8sClient.setConnectTimeout(Integer.parseInt(System.getProperty(ATHENZ_K8S_CONNECT_TIMEOUT, "500")));
+ this.k8sClient.setReadTimeout(Integer.parseInt(System.getProperty(ATHENZ_K8S_READ_TIMEOUT, "2000")));
+ Configuration.setDefaultApiClient(k8sClient);
+ }
+
+ @Override
+ public ServerPrivateKey getPrivateKey(String service, String namespace,
+ String secretName, String algorithm) {
+ String keyName;
+ String keyIdName;
+ final String objectSuffix = "." + algorithm.toLowerCase();
+ if (ZMS_SERVICE.equals(service)) {
+ keyName = System.getProperty(ATHENZ_PROP_K8S_ZMS_KEY_NAME, ATHENZ_K8S_DEFAULT_KEY_NAME) + objectSuffix;
+ keyIdName = System.getProperty(ATHENZ_PROP_K8S_ZMS_KEY_ID_NAME, ATHENZ_K8S_DEFAULT_KEY_ID_NAME) + objectSuffix;
+ } else if (ZTS_SERVICE.equals(service)) {
+ keyName = System.getProperty(ATHENZ_PROP_K8S_ZTS_KEY_NAME, ATHENZ_K8S_DEFAULT_KEY_NAME) + objectSuffix;
+ keyIdName = System.getProperty(ATHENZ_PROP_K8S_ZTS_KEY_ID_NAME, ATHENZ_K8S_DEFAULT_KEY_ID_NAME) + objectSuffix;
+ } else if (MSD_SERVICE.equals(service)) {
+ keyName = System.getProperty(ATHENZ_PROP_K8S_MSD_KEY_NAME, ATHENZ_K8S_DEFAULT_KEY_NAME) + objectSuffix;
+ keyIdName = System.getProperty(ATHENZ_PROP_K8S_MSD_KEY_ID_NAME, ATHENZ_K8S_DEFAULT_KEY_ID_NAME) + objectSuffix;
+ } else {
+ LOG.error("Unknown service specified: {}", service);
+ return null;
+ }
+
+ PrivateKey pkey = null;
+ try {
+ pkey = Crypto.loadPrivateKey(getSecretFromK8S(namespace, secretName, keyName));
+ } catch (Exception ex) {
+ LOG.error("unable to load private key", ex);
+ }
+ return pkey == null ? null : new ServerPrivateKey(pkey, getSecretFromK8S(namespace, secretName, keyIdName));
+ }
+
+ @Override
+ public char[] getSecret(String namespace, String secretName, String keyName) {
+ return getSecretFromK8S(namespace, secretName, keyName).toCharArray();
+ }
+
+ String getSecretFromK8S(String namespace, String secretName, String keyName) {
+ try {
+ CoreV1Api api = new CoreV1Api(k8sClient);
+ V1Secret secret = api.readNamespacedSecret(secretName, namespace).execute();
+ if (secret != null && secret.getData() != null && secret.getData().get(keyName) != null) {
+ return new String(secret.getData().get(keyName), StandardCharsets.UTF_8);
+ } else {
+ LOG.error("Unable to retrieve secret={} for key={} from namespace={}", secretName, keyName, namespace);
+ return "";
+ }
+ } catch (ApiException e) {
+ LOG.error("Error in retrieving secret={} for key={} from namespace={}", secretName, keyName, namespace);
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/libs/java/server_k8s_common/src/main/java/io/athenz/server/k8s/common/impl/KubernetesSecretPrivateKeyStoreFactory.java b/libs/java/server_k8s_common/src/main/java/io/athenz/server/k8s/common/impl/KubernetesSecretPrivateKeyStoreFactory.java
new file mode 100644
index 00000000000..cb8b0781058
--- /dev/null
+++ b/libs/java/server_k8s_common/src/main/java/io/athenz/server/k8s/common/impl/KubernetesSecretPrivateKeyStoreFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * Licensed 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 io.athenz.server.k8s.common.impl;
+
+import com.yahoo.athenz.auth.PrivateKeyStore;
+import com.yahoo.athenz.auth.PrivateKeyStoreFactory;
+import io.kubernetes.client.util.Config;
+
+public class KubernetesSecretPrivateKeyStoreFactory implements PrivateKeyStoreFactory {
+ @Override
+ public PrivateKeyStore create() {
+ try {
+ return new KubernetesSecretPrivateKeyStore(Config.defaultClient());
+ } catch (Exception ex) {
+ throw new RuntimeException("Unable to create KubernetesSecretPrivateKeyStore", ex);
+ }
+ }
+}
diff --git a/libs/java/server_k8s_common/src/test/java/io/athenz/server/k8s/common/impl/KubernetesSecretPrivateKeyStoreFactoryTest.java b/libs/java/server_k8s_common/src/test/java/io/athenz/server/k8s/common/impl/KubernetesSecretPrivateKeyStoreFactoryTest.java
new file mode 100644
index 00000000000..5ee9702fe97
--- /dev/null
+++ b/libs/java/server_k8s_common/src/test/java/io/athenz/server/k8s/common/impl/KubernetesSecretPrivateKeyStoreFactoryTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * Licensed 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 io.athenz.server.k8s.common.impl;
+
+import com.yahoo.athenz.auth.PrivateKeyStore;
+import io.kubernetes.client.util.Config;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+public class KubernetesSecretPrivateKeyStoreFactoryTest {
+ @Test
+ public void createKubernetesSecretPrivateKeyStore() {
+ PrivateKeyStore privateKeyStore = new KubernetesSecretPrivateKeyStoreFactory().create();
+ assertTrue(privateKeyStore instanceof KubernetesSecretPrivateKeyStore);
+ }
+
+ @Test
+ public void createKubernetesSecretPrivateKeyStoreException() {
+ try (MockedStatic configMockedStatic = Mockito.mockStatic(Config.class)) {
+ configMockedStatic.when(Config::defaultClient).thenThrow(new RuntimeException("mocked exception"));
+ try {
+ new KubernetesSecretPrivateKeyStoreFactory().create();
+ fail();
+ } catch (RuntimeException ex) {
+ assertTrue(ex.getMessage().contains("Unable to create KubernetesSecretPrivateKeyStore"));
+ }
+ }
+ }
+}
diff --git a/libs/java/server_k8s_common/src/test/java/io/athenz/server/k8s/common/impl/KubernetesSecretPrivateKeyStoreTest.java b/libs/java/server_k8s_common/src/test/java/io/athenz/server/k8s/common/impl/KubernetesSecretPrivateKeyStoreTest.java
new file mode 100644
index 00000000000..0443abcd91e
--- /dev/null
+++ b/libs/java/server_k8s_common/src/test/java/io/athenz/server/k8s/common/impl/KubernetesSecretPrivateKeyStoreTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * Licensed 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 io.athenz.server.k8s.common.impl;
+
+import com.yahoo.athenz.auth.PrivateKeyStore;
+import com.yahoo.athenz.auth.ServerPrivateKey;
+import io.kubernetes.client.openapi.ApiClient;
+import okhttp3.HttpUrl;
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import org.mockito.Mockito;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+import java.io.FileInputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import static org.testng.Assert.*;
+
+public class KubernetesSecretPrivateKeyStoreTest {
+ private KubernetesSecretPrivateKeyStoreFactory getFactory(final ApiClient k8sClient) {
+ return new KubernetesSecretPrivateKeyStoreFactory() {
+ @Override
+ public PrivateKeyStore create() {
+ return new KubernetesSecretPrivateKeyStore(k8sClient);
+ }
+ };
+ }
+
+ private MockWebServer server;
+
+ @Test
+ public void testGetSecret() throws Exception {
+ server = new MockWebServer();
+ Path path = Paths.get("src/test/resources/sample-secret-response.json");
+ try (FileInputStream fis = new FileInputStream(path.toFile())) {
+ server.enqueue(new MockResponse().setBody(new String(fis.readAllBytes())));
+ server.start();
+ ApiClient k8sClient = Mockito.spy(new ApiClient());
+ HttpUrl baseUrl = server.url("/api/v1/namespaces/myns/secrets/mysecret");
+ k8sClient.setBasePath(baseUrl.toString());
+ KubernetesSecretPrivateKeyStoreFactory factory = getFactory(k8sClient);
+ assertEquals(factory.create().getSecret("myns", "mysecret", "password"), new char[]{'c', 'h', 'a', 'n', 'g', 'e', 'i', 't'});
+ }
+ }
+
+ @Test
+ public void testGetSecretMissing() throws Exception {
+ server = new MockWebServer();
+ Path path = Paths.get("src/test/resources/invalid-secret-key-response.json");
+ try (FileInputStream fis = new FileInputStream(path.toFile())) {
+ server.enqueue(new MockResponse().setBody(new String(fis.readAllBytes())));
+ server.start();
+ ApiClient k8sClient = Mockito.spy(new ApiClient());
+ HttpUrl baseUrl = server.url("/api/v1/namespaces/myns/secrets/mysecret");
+ k8sClient.setBasePath(baseUrl.toString());
+ KubernetesSecretPrivateKeyStoreFactory factory = getFactory(k8sClient);
+ assertEquals(factory.create().getSecret("myns", "mysecret", "password"), new char[]{});
+ }
+ }
+
+ @Test
+ public void testGetPrivateKey() throws Exception {
+ server = new MockWebServer();
+ Path path = Paths.get("src/test/resources/sample-secret-key-response.json");
+ byte[] keyBytes;
+ try (FileInputStream fis = new FileInputStream(path.toFile())) {
+ keyBytes = fis.readAllBytes();
+ //mock response for zms key
+ server.enqueue(new MockResponse().setBody(new String(keyBytes)));
+ //mock response for zms key id
+ server.enqueue(new MockResponse().setBody(new String(keyBytes)));
+ //mock response for zts key
+ server.enqueue(new MockResponse().setBody(new String(keyBytes)));
+ //mock response for zts key id
+ server.enqueue(new MockResponse().setBody(new String(keyBytes)));
+ //mock response for msd key
+ server.enqueue(new MockResponse().setBody(new String(keyBytes)));
+ //mock response for msd key id
+ server.enqueue(new MockResponse().setBody(new String(keyBytes)));
+ server.start();
+ ApiClient k8sClient = Mockito.spy(new ApiClient());
+ HttpUrl baseUrl = server.url("/api/v1/namespaces/myns/secrets/mysecret");
+ k8sClient.setBasePath(baseUrl.toString());
+ KubernetesSecretPrivateKeyStoreFactory factory = getFactory(k8sClient);
+ KubernetesSecretPrivateKeyStore store = (KubernetesSecretPrivateKeyStore) factory.create();
+ assertNotNull(store.getPrivateKey("zms", "myns","mysecret", "EC"));
+ assertNotNull(store.getPrivateKey("zts", "myns","mysecret", "EC"));
+ assertNotNull(store.getPrivateKey("msd", "myns","mysecret", "EC"));
+ // no mock response present so expected a read timeout
+ assertNull(store.getPrivateKey("msd", "myns","mysecret", "EC"));
+ assertNull(store.getPrivateKey("unknown", "myns","mysecret", "EC"));
+ }
+ }
+
+ @AfterMethod
+ public void tearDown() throws Exception {
+ server.shutdown();
+ }
+}
\ No newline at end of file
diff --git a/libs/java/server_k8s_common/src/test/resources/invalid-secret-key-response.json b/libs/java/server_k8s_common/src/test/resources/invalid-secret-key-response.json
new file mode 100644
index 00000000000..7ad2cb1c114
--- /dev/null
+++ b/libs/java/server_k8s_common/src/test/resources/invalid-secret-key-response.json
@@ -0,0 +1 @@
+{"kind":"Secret","apiVersion":"v1","metadata":{"name":"mysecret","namespace":"myns","uid":"68427c0d-a549-4a44-b938-498d57a541af","resourceVersion":"118061","creationTimestamp":"2024-05-29T20:49:54Z","managedFields":[{"manager":"kubectl-create","operation":"Update","apiVersion":"v1","time":"2024-05-29T20:49:54Z","fieldsType":"FieldsV1","fieldsV1":{"f:data":{".":{},"f:service_k8s_private_key.ec":{}},"f:type":{}}}]},"data":{"service_k8s_private_key.ec":"djAK","service_k8s_private_key_id.ec": "djAK"},"type":"Opaque"}
\ No newline at end of file
diff --git a/libs/java/server_k8s_common/src/test/resources/sample-secret-key-response.json b/libs/java/server_k8s_common/src/test/resources/sample-secret-key-response.json
new file mode 100644
index 00000000000..834c0287efc
--- /dev/null
+++ b/libs/java/server_k8s_common/src/test/resources/sample-secret-key-response.json
@@ -0,0 +1 @@
+{"kind":"Secret","apiVersion":"v1","metadata":{"name":"mysecret","namespace":"myns","uid":"68427c0d-a549-4a44-b938-498d57a541af","resourceVersion":"118061","creationTimestamp":"2024-05-29T20:49:54Z","managedFields":[{"manager":"kubectl-create","operation":"Update","apiVersion":"v1","time":"2024-05-29T20:49:54Z","fieldsType":"FieldsV1","fieldsV1":{"f:data":{".":{},"f:service_k8s_private_key.ec":{}},"f:type":{}}}]},"data":{"service_k8s_private_key.ec":"LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUNQbmFTaGRlY0xyMDViV0I2SnBrTjlGc1FVUndzam5GZkRmNk5VcGo5V0RvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFalRIckFSU1RsUFNHeVZwUHpjTTFYTG12M3hlY2JzY0NOREtlTUt0eDBKNEJOMVhaNXVsNQorb0dXTDlKZG5DOHZmN3M2SVBjeE92SVp0SDdORklWbit3PT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=","service_k8s_private_key_id.ec": "djAK"},"type":"Opaque"}
\ No newline at end of file
diff --git a/libs/java/server_k8s_common/src/test/resources/sample-secret-response.json b/libs/java/server_k8s_common/src/test/resources/sample-secret-response.json
new file mode 100644
index 00000000000..145cbbc7d48
--- /dev/null
+++ b/libs/java/server_k8s_common/src/test/resources/sample-secret-response.json
@@ -0,0 +1 @@
+{"kind":"Secret","apiVersion":"v1","metadata":{"name":"mysecret","namespace":"myns","uid":"68427c0d-a549-4a44-b938-498d57a541af","resourceVersion":"118061","creationTimestamp":"2024-05-29T20:49:54Z","managedFields":[{"manager":"kubectl-create","operation":"Update","apiVersion":"v1","time":"2024-05-29T20:49:54Z","fieldsType":"FieldsV1","fieldsV1":{"f:data":{".":{},"f:password":{}},"f:type":{}}}]},"data":{"password":"Y2hhbmdlaXQ="},"type":"Opaque"}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 1e64e719f9b..131850a21d5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -93,11 +93,13 @@
11.0.21
0.11.5
5.14.0
+ 20.0.1
1.2.13
1.5.6
5.12.0
8.4.0
9.39.1
+ 4.12.0
1.5.4
1.7.36
2.0.13
@@ -145,6 +147,7 @@
libs/java/client_common
libs/java/cert_refresher
libs/java/server_common
+ libs/java/server_k8s_common
libs/java/syncer_common
libs/java/instance_provider
libs/java/dynamodb_client_factory