diff --git a/README.md b/README.md index a2eefb0b..6cef19ae 100644 --- a/README.md +++ b/README.md @@ -295,6 +295,7 @@ Parameter | Type | User Property | Required | Description `` | boolean | helm.dependency-build.skip | false | skip dependency-build goal `` | boolean | helm.package.skip | false | skip package goal `` | boolean | helm.upload.skip | false | skip upload goal +`` | boolean | helm.upload.insecure | false | Skip tls certificate checks for the chart upload. `` | boolean | helm.install.skip | false | skip install goal `` | string | helm.security | false | path to your [settings-security.xml](https://maven.apache.org/guides/mini/guide-encryption.html) (default: `~/.m2/settings-security.xml`) `` | string | helm.package.keyring | false | path to gpg secret keyring for signing diff --git a/src/main/java/io/kokuwa/maven/helm/UploadMojo.java b/src/main/java/io/kokuwa/maven/helm/UploadMojo.java index b301b5f4..5e424bd8 100644 --- a/src/main/java/io/kokuwa/maven/helm/UploadMojo.java +++ b/src/main/java/io/kokuwa/maven/helm/UploadMojo.java @@ -14,10 +14,8 @@ import java.security.cert.X509Certificate; import java.util.Base64; -import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; @@ -126,61 +124,52 @@ private void uploadSingle(Path chart) throws MojoExecutionException, IOException HelmRepository uploadRepo = getHelmUploadRepo(); HttpURLConnection connection; - if (uploadRepo.getType() == null) { throw new IllegalArgumentException("Repository type missing. Check your plugin configuration."); } - SSLSocketFactory dsf = HttpsURLConnection.getDefaultSSLSocketFactory(); - HostnameVerifier dhv = HttpsURLConnection.getDefaultHostnameVerifier(); - try { - if (insecure) { - setupInsecureTLS(); - } + switch (uploadRepo.getType()) { + case ARTIFACTORY: + connection = getConnectionForUploadToArtifactory(fileToUpload, uploadRepo); + break; + case CHARTMUSEUM: + connection = getConnectionForUploadToChartMuseum(); + break; + case NEXUS: + connection = getConnectionForUploadToNexus(fileToUpload); + break; + default: + throw new IllegalArgumentException("Unsupported repository type for upload."); + } - switch (uploadRepo.getType()) { - case ARTIFACTORY: - connection = getConnectionForUploadToArtifactory(fileToUpload, uploadRepo); - break; - case CHARTMUSEUM: - connection = getConnectionForUploadToChartMuseum(); - break; - case NEXUS: - connection = getConnectionForUploadToNexus(fileToUpload); - break; - default: - throw new IllegalArgumentException("Unsupported repository type for upload."); - } + if (insecure && connection instanceof HttpsURLConnection) { + setupInsecureTLS((HttpsURLConnection) connection); + } - try (FileInputStream fileInputStream = new FileInputStream(fileToUpload)) { - IOUtils.copy(fileInputStream, connection.getOutputStream()); - } - if (connection.getResponseCode() >= 300) { - String response; - if (connection.getErrorStream() != null) { - response = new String(IOUtils.toByteArray(connection.getErrorStream())); - } else if (connection.getInputStream() != null) { - response = new String(IOUtils.toByteArray(connection.getInputStream())); - } else { - response = "No details provided"; - } - throw new MojoExecutionException("Failed to upload: " + response); + try (FileInputStream fileInputStream = new FileInputStream(fileToUpload)) { + IOUtils.copy(fileInputStream, connection.getOutputStream()); + } + if (connection.getResponseCode() >= 300) { + String response; + if (connection.getErrorStream() != null) { + response = new String(IOUtils.toByteArray(connection.getErrorStream())); + } else if (connection.getInputStream() != null) { + response = new String(IOUtils.toByteArray(connection.getInputStream())); } else { - String message = Integer.toString(connection.getResponseCode()); - if (connection.getInputStream() != null) { - message += " - " + new String(IOUtils.toByteArray(connection.getInputStream())); - } - getLog().info(message); - } - connection.disconnect(); - } finally { - if (dsf != null) { - HttpsURLConnection.setDefaultSSLSocketFactory(dsf); + response = "No details provided"; } - if (dhv != null) { - HttpsURLConnection.setDefaultHostnameVerifier(dhv); + throw new MojoExecutionException("Failed to upload: " + response); + } else { + String message = "Returned: " + connection.getResponseCode(); + if (connection.getInputStream() != null) { + String details = new String(IOUtils.toByteArray(connection.getInputStream())); + if (!details.isEmpty()) { + message += " - " + details; + } } + getLog().info(message); } + connection.disconnect(); } private HttpURLConnection getConnectionForUploadToChartMuseum() throws IOException, MojoExecutionException { @@ -251,41 +240,36 @@ private HttpURLConnection getConnectionForUploadToNexus(File file) throws IOExce return connection; } - private void setupInsecureTLS() throws MojoExecutionException { - // Create a trust manager that does not validate certificate chains - TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { + private void setupInsecureTLS(HttpsURLConnection connection) throws MojoExecutionException { + + TrustManager trustManager = new X509TrustManager() { + + @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return null; //NOSONAR + return null; } - public void checkClientTrusted(X509Certificate[] certs, String authType) { //NOSONAR - // no-op - } + @Override + public void checkClientTrusted(X509Certificate[] certs, String authType) {} - public void checkServerTrusted(X509Certificate[] certs, String authType) { //NOSONAR - // no-op - } - } }; + @Override + public void checkServerTrusted(X509Certificate[] certs, String authType) {} + }; - // Install the all-trusting trust manager SSLContext sc; try { sc = SSLContext.getInstance("TLS"); - } catch (NoSuchAlgorithmException e) { - throw new MojoExecutionException("Cannot create TLS context instance", e); - } - try { - sc.init(null, trustAllCerts, new java.security.SecureRandom()); + sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom()); } catch (KeyManagementException e) { throw new MojoExecutionException("Cannot initialize TLS context instance", e); + } catch (NoSuchAlgorithmException e) { + throw new MojoExecutionException("Cannot create TLS context instance", e); } - HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); - // Create all-trusting host name verifier - HostnameVerifier allHostsValid = (hostname, session) -> true; //NOSONAR + connection.setSSLSocketFactory(sc.getSocketFactory()); + connection.setHostnameVerifier((hostname, session) -> true); - // Install the all-trusting host verifier - HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid); + getLog().info("Use insecure TLS connection for [" + connection.getURL() + "]"); } /** diff --git a/src/test/java/io/kokuwa/maven/helm/UploadMojoTest.java b/src/test/java/io/kokuwa/maven/helm/UploadMojoTest.java index d4cc9fe7..542b0e5e 100644 --- a/src/test/java/io/kokuwa/maven/helm/UploadMojoTest.java +++ b/src/test/java/io/kokuwa/maven/helm/UploadMojoTest.java @@ -9,11 +9,13 @@ import java.util.List; import org.apache.hc.core5.http.HttpHeaders; +import org.apache.maven.plugin.MojoExecutionException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.http.RequestMethod; import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder; @@ -26,7 +28,10 @@ public class UploadMojoTest extends AbstractMojoTest { @RegisterExtension - static WireMockExtension mock = WireMockExtension.newInstance().failOnUnmatchedRequests(true).build(); + static WireMockExtension mock = WireMockExtension.newInstance() + .failOnUnmatchedRequests(true) + .options(WireMockConfiguration.wireMockConfig().dynamicPort().dynamicHttpsPort()) + .build(); @DisplayName("no tar gz present") @Test @@ -96,6 +101,30 @@ void urlChartmuseumWithServerIdEncrypted(UploadMojo mojo) { assertUpload(mojo, RequestMethod.POST, "/chartmuseum", BASIC_FOO_SECRET); } + @DisplayName("nexus: tls fail because of selfsigned certificate") + @Test + void urlNexusTlsFail(UploadMojo mojo) { + mojo.setInsecure(false); + mojo.setUploadRepoStable(new HelmRepository() + .setType(RepoType.NEXUS) + .setName("my-nexus") + .setUrl("https://127.0.0.1:" + mock.getHttpsPort() + "/nexus")); + copyPackagedHelmChartToOutputdirectory(mojo); + assertThrows(MojoExecutionException.class, mojo::execute, "upload failed"); + } + + @DisplayName("nexus: tls insecure with selfsigned certificate") + @Test + void urlNexusTlsInsecure(UploadMojo mojo) { + mojo.setInsecure(true); + mojo.setUploadRepoStable(new HelmRepository() + .setType(RepoType.NEXUS) + .setName("my-nexus") + .setUrl("https://127.0.0.1:" + mock.getHttpsPort() + "/nexus")); + Path packaged = copyPackagedHelmChartToOutputdirectory(mojo); + assertUpload(mojo, RequestMethod.PUT, "/nexus/" + packaged.getFileName(), null); + } + @DisplayName("nexus: without authentication") @Test void urlNexusWithoutAuthentication(UploadMojo mojo) { @@ -280,7 +309,9 @@ private void assertUpload(UploadMojo mojo, RequestMethod method, String path, St assertEquals(1, requests.size(), "expected only one request"); LoggedRequest request = requests.get(0); assertEquals(method, request.getMethod(), "method"); - assertEquals("http://127.0.0.1:" + mock.getPort() + path, request.getAbsoluteUrl(), "url"); + assertEquals((mojo.getUploadRepoStable().getUrl().startsWith("https") + ? "https://127.0.0.1:" + mock.getHttpsPort() + : "http://127.0.0.1:" + mock.getPort()) + path, request.getAbsoluteUrl(), "url"); assertEquals("application/gzip", request.getHeader(HttpHeaders.CONTENT_TYPE), "content-type"); assertEquals(authorization, request.getHeader(HttpHeaders.AUTHORIZATION), "authorization"); }