Repository: hazelcast/hazelcast-kubernetes
Pull request #191 (2020-01-29 11:52:24)

Fix Multiple CA in ca.crt file (port for 3.12.x)
Port of #190 for 3.12.x.


Patch:
diff --git a/pom.xml b/pom.xml
index 5a87d4c..3d62573 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,12 +45,12 @@
 
     <log4j.version>1.2.12</log4j.version>
     <junit.version>4.12</junit.version>
-    <wiremock.version>2.18.0</wiremock.version>
+    <wiremock.version>2.25.1</wiremock.version>
     <hamcrest.version>1.3</hamcrest.version>
     <mockito.version>1.10.19</mockito.version>
     <powermock.version>1.7.3</powermock.version>
 
-    <hazelcast.version>3.12.4</hazelcast.version>
+    <hazelcast.version>3.12.5</hazelcast.version>
 
     <maven.checkstyle.plugin.version>2.15</maven.checkstyle.plugin.version>
     <maven.findbugs.plugin.version>3.0.4</maven.findbugs.plugin.version>
diff --git a/src/main/java/com/hazelcast/kubernetes/KubernetesClient.java b/src/main/java/com/hazelcast/kubernetes/KubernetesClient.java
index 438210e..040f828 100644
--- a/src/main/java/com/hazelcast/kubernetes/KubernetesClient.java
+++ b/src/main/java/com/hazelcast/kubernetes/KubernetesClient.java
@@ -491,7 +491,7 @@ class KubernetesClient {
             public JsonObject call() {
                 return Json
                         .parse(RestClient.create(urlString).withHeader("Authorization", String.format("Bearer %s", apiToken))
-                                         .withCaCertificate(caCertificate)
+                                         .withCaCertificates(caCertificate)
                                          .get())
                         .asObject();
             }
diff --git a/src/main/java/com/hazelcast/kubernetes/RestClient.java b/src/main/java/com/hazelcast/kubernetes/RestClient.java
index 608dc7c..72e47cf 100644
--- a/src/main/java/com/hazelcast/kubernetes/RestClient.java
+++ b/src/main/java/com/hazelcast/kubernetes/RestClient.java
@@ -35,6 +35,7 @@ import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Scanner;
 
@@ -69,7 +70,7 @@ final class RestClient {
         return this;
     }
 
-    RestClient withCaCertificate(String caCertificate) {
+    RestClient withCaCertificates(String caCertificate) {
         this.caCertificate = caCertificate;
         return this;
     }
@@ -175,7 +176,12 @@ final class RestClient {
         try {
             KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
             keyStore.load(null, null);
-            keyStore.setCertificateEntry("ca", generateCertificate());
+
+            int i = 0;
+            for (Certificate certificate : generateCertificates()) {
+                String alias = String.format("ca-%d", i++);
+                keyStore.setCertificateEntry(alias, certificate);
+            }
 
             TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
             tmf.init(keyStore);
@@ -192,13 +198,13 @@ final class RestClient {
     /**
      * Generates CA Certificate from the default CA Cert file or from the externally provided "ca-certificate" property.
      */
-    private Certificate generateCertificate()
+    private Collection<? extends Certificate> generateCertificates()
             throws IOException, CertificateException {
         InputStream caInput = null;
         try {
             CertificateFactory cf = CertificateFactory.getInstance("X.509");
             caInput = new ByteArrayInputStream(caCertificate.getBytes("UTF-8"));
-            return cf.generateCertificate(caInput);
+            return cf.generateCertificates(caInput);
         } finally {
             IOUtil.closeResource(caInput);
         }


Test patch:
diff --git a/src/test/java/com/hazelcast/kubernetes/RestClientTest.java b/src/test/java/com/hazelcast/kubernetes/RestClientTest.java
index fe2e7eb..13804f5 100644
--- a/src/test/java/com/hazelcast/kubernetes/RestClientTest.java
+++ b/src/test/java/com/hazelcast/kubernetes/RestClientTest.java
@@ -37,13 +37,14 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
-import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
-import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
-import static com.github.tomakehurst.wiremock.client.WireMock.get;
-import static com.github.tomakehurst.wiremock.client.WireMock.post;
-import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
-import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSession;
+import java.io.File;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.*;
 import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static com.hazelcast.kubernetes.KubernetesConfig.readFileContents;
 import static org.junit.Assert.assertEquals;
 
 public class RestClientTest {
@@ -52,13 +53,23 @@ public class RestClientTest {
     private static final String BODY_RESPONSE = "some body response";
 
     @Rule
-    public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort());
+    public WireMockRule wireMockRule = new WireMockRule(wireMockConfig()
+            .dynamicHttpsPort()
+            .keystorePath(pathTo("keystore.jks"))
+    );
 
     private String address;
 
     @Before
     public void setUp() {
-        address = String.format("http://localhost:%s", wireMockRule.port());
+        // disable hostname HTTPS verification for testing
+        HttpsURLConnection.setDefaultHostnameVerifier(
+                new HostnameVerifier() {
+                    public boolean verify(String hostname, SSLSession sslSession) {
+                        return true;
+                    }
+                });
+        address = String.format("https://localhost:%s", wireMockRule.httpsPort());
     }
 
     @Test
@@ -68,7 +79,9 @@ public class RestClientTest {
                 .willReturn(aResponse().withStatus(200).withBody(BODY_RESPONSE)));
 
         // when
-        String result = RestClient.create(String.format("%s%s", address, API_ENDPOINT)).get();
+        String result = RestClient.create(String.format("%s%s", address, API_ENDPOINT))
+                .withCaCertificates(readFile("ca.crt"))
+                .get();
 
         // then
         assertEquals(BODY_RESPONSE, result);
@@ -85,8 +98,9 @@ public class RestClientTest {
 
         // when
         String result = RestClient.create(String.format("%s%s", address, API_ENDPOINT))
-                                  .withHeader(headerKey, headerValue)
-                                  .get();
+                .withHeader(headerKey, headerValue)
+                .withCaCertificates(readFile("ca.crt"))
+                .get();
 
         // then
         assertEquals(BODY_RESPONSE, result);
@@ -99,7 +113,9 @@ public class RestClientTest {
                 .willReturn(aResponse().withStatus(500).withBody("Internal error")));
 
         // when
-        RestClient.create(String.format("%s%s", address, API_ENDPOINT)).get();
+        RestClient.create(String.format("%s%s", address, API_ENDPOINT))
+                .withCaCertificates(readFile("ca.crt"))
+                .get();
 
         // then
         // throw exception
@@ -114,10 +130,19 @@ public class RestClientTest {
 
         // when
         String result = RestClient.create(String.format("%s%s", address, API_ENDPOINT))
-                                  .withBody(BODY_REQUEST)
-                                  .post();
+                .withBody(BODY_REQUEST)
+                .withCaCertificates(readFile("ca.crt"))
+                .post();
 
         // then
         assertEquals(BODY_RESPONSE, result);
     }
+
+    private String readFile(String filename) {
+        return readFileContents(pathTo(filename));
+    }
+
+    private String pathTo(String filename) {
+        return new File(getClass().getClassLoader().getResource(filename).getFile()).getAbsolutePath();
+    }
 }
diff --git a/src/test/resources/ca.crt b/src/test/resources/ca.crt
new file mode 100644
index 0000000..4377bc7
--- /dev/null
+++ b/src/test/resources/ca.crt
@@ -0,0 +1,58 @@
+-----BEGIN CERTIFICATE-----
+MIIDMjCCAhqgAwIBAgIIbACAU0ZsknAwDQYJKoZIhvcNAQELBQAwNzESMBAGA1UE
+CxMJb3BlbnNoaWZ0MSEwHwYDVQQDExhrdWJlLWFwaXNlcnZlci1sYi1zaWduZXIw
+HhcNMjAwMTIyMjE0MzE4WhcNMzAwMTE5MjE0MzE4WjA3MRIwEAYDVQQLEwlvcGVu
+c2hpZnQxITAfBgNVBAMTGGt1YmUtYXBpc2VydmVyLWxiLXNpZ25lcjCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBANX8oMkxb4skmWGLXQxTbjYO50EZJ0uD
+AgCQs/LDCWmNSbUdjUsCSLqQCadSEiH7f4VqtYyCfQMfcf3vtaebLL/qZ4RSQy67
+xw6bYyN7Qh/ryNSPoczr0GXkrho4CYehRfnLaehUj6bGRe0Rhz5gisRMQ3jftKRj
+rY0FP18R4kbSyMVDZWtc82WGTUxXc2gyklTWzPUVLihHAJw/ip39TmmewvYqCYhh
+GGAZDbKVfVzZ9145/9K0EbVBRDn6lAWfh/44yrWy9cZAUidXFxLTo2YWoNzb5QJn
+YPt/EXhaANS3E5SPgQrlZsbxXKMfACTPWc2Y9Wb4ES89NNP1bl4DC4sCAwEAAaNC
+MEAwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJWe
+blS+7Fg7apqyu3sMraGZ34OoMA0GCSqGSIb3DQEBCwUAA4IBAQCu3BbKy3H85zEY
+hJTmCsRd4ZUyFjQd2D4Z0bZjdprbrW83P7UoHSA3giebvABrIzdrSANTfRLYTXCT
+LuxBwcY9IabxggL9Ki8p80NoKC4sWwLO3qFLGNFUTCOVU7gl7RHaHLVpHUoGFRh1
+kFPWnGnH1RniuY0vte8dYbUgKCzHdOW70ZdfxADwVwj1G31/KO93yO3BbX7d8IYa
+mDvHco/FQWxDK1MigAOCRBpPAEdj+wz1DfnCx9ZqVy5AV1XChoEkK1ZROzHIH+mc
+u5HFZn0ce0++aTZfnYVja7hGaFVf5rGi5i2NnACYbtT7rCXq9y/CsMb78HRly/En
+adDce2Y3
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIICeDCCAeGgAwIBAgIECFbWTjANBgkqhkiG9w0BAQsFADBuMRAwDgYDVQQGEwdV
+bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
+VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRIwEAYDVQQDEwlsb2NhbGhv
+c3QwIBcNMjAwMTI4MTU1MDQ4WhgPMjEyMDAxMDQxNTUwNDhaMG4xEDAOBgNVBAYT
+B1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vua25vd24xEDAO
+BgNVBAoTB1Vua25vd24xEDAOBgNVBAsTB1Vua25vd24xEjAQBgNVBAMTCWxvY2Fs
+aG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqUga3JPPycHEsHS7LCRi
+zZ9nruarwTzKSGoLUtJEH5SgmFucK+cJs0SvXWVTJ4BRlsyOjHYxnJN1gBSbzLt9
+MkaoxzYfqPoQYuePOjFiBsmbbLBEk4yZhUB/ik4yLzu/aGUwuyJQayMNo3Th1WQ4
+3qxzSdWAASPgBenOugiWTFECAwEAAaMhMB8wHQYDVR0OBBYEFARNaKaAcaGEuezm
+meCv76mqmPVdMA0GCSqGSIb3DQEBCwUAA4GBAGznOs7mw+Swnzy3jtmLms1ajRlb
+qhCQtd3d/I5NJYpq7z8n/5fkNx1orLcQ1cLQpoHQ4TIKjlrOlJnDp0m3i3fFyLv3
+/zguGfAg9Fh//uY2fqd1H0f6tCd4631rTqH1aN15OFfyRGIItxp2xO+NEl2yMyc8
+CyJtWtjG31l3ayBB
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDQDCCAiigAwIBAgIIaDKbna6B3DMwDQYJKoZIhvcNAQELBQAwPjESMBAGA1UE
+CxMJb3BlbnNoaWZ0MSgwJgYDVQQDEx9rdWJlLWFwaXNlcnZlci1sb2NhbGhvc3Qt
+c2lnbmVyMB4XDTIwMDEyMjIxNDMxOFoXDTMwMDExOTIxNDMxOFowPjESMBAGA1UE
+CxMJb3BlbnNoaWZ0MSgwJgYDVQQDEx9rdWJlLWFwaXNlcnZlci1sb2NhbGhvc3Qt
+c2lnbmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApAxgtdFW7XX3
+KPeWAIFK7+mT2YMRxZo/WEMVEU56duWQJQ5vSOr2nxvMvuvmTRbMDMouDk2xllsk
+AkB8osibpH4onK44RNXROLylnsEvANv7UhN1vAIfB5G6eoR/Lh6HMaeL/7jMUyxY
+bKjQgUqgtzaNunNfgrpgB6TN8Kl6PD0kN+/SXZF1+VBvoptei0utBa1PGbHmGSid
+2dy96CUKnlJBISLwW4kPdI6j9bl8+wbf50YB4f5cyiaQY7AwhBwZX1QsZNy74mXw
+wNdre8YZF7ZwVpJ1Is7khFduEL5ya6PaU7/wNo5oejwkQ+4hDSptVl5/n3gJDhgg
+3JMxufArrwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAqQwDwYDVR0TAQH/BAUwAwEB
+/zAdBgNVHQ4EFgQUmGd2zDD68DdK2qOvVaOdL7jRGacwDQYJKoZIhvcNAQELBQAD
+ggEBAAejSalM9im0Q4BIXw1JBwVCXvkL9z1USE+wB2rf3oS/GScNnpP2eMTpSoF0
+U1ZYoHn/ARKKQX8SLmBmgUjasi7ZTZPpESh+hyMvAVyp7F8a1y0DiL4UIAksds4h
+8jiW98paKuhQR3wAX54Q9n48LvusrRQVdEWyXsH30S1FkMawuzZgh5G0DvpaNgpP
+b0syMDonXJ2yOkAGCr3Cm7zfhbfX09hsgoWh8ynMahtE4vXEDE7k+S7Brukn7uac
+G/mUQBQYIjzRKgzPN54H0tCfTj+vDRYw9JuKEYPjOfK1+udMVx3zO/TjlnjOpH73
+phFZaYFEgK7MESMl9oqpHYe/GJQ=
+-----END CERTIFICATE-----