From 8f8742b70176609f0c11ea1cd929868aa30dda82 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Mon, 31 Aug 2026 11:00:12 +0200 Subject: [PATCH] fix(registry): honor plainHttp on registry login RegistryLogin built its registry client with plainHTTP but never passed action.WithPlainHTTPLogin to the login action, so the flag was silently ignored. This worked until oras-go v2.6.1, a security release that stopped forwarding credentials across an HTTPS->HTTP downgrade (GHSA-28r5-37g7-p6mp, GHSA-xf85-363p-868w) - the insecure fallback had been carrying them. The Go and Java OCI tests relied on that same fallback, so they now declare plainHttp explicitly against the plain-HTTP test registry. Also bumps GO_VERSION to 1.26.7: Go 1.25 is out of support and helm.sh/helm/v3 3.21.4 requires go >= 1.26.0. Unblocks #419 and #420. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Marc Nuri --- .github/workflows/build.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/snapshots.yml | 2 +- .../java/com/marcnuri/helm/HelmPushTest.java | 10 ++++-- .../com/marcnuri/helm/HelmRegistryTest.java | 12 +++---- .../java/com/marcnuri/helm/HelmShowTest.java | 6 ++-- native/internal/helm/registry.go | 1 + native/main_test.go | 33 +++++++++++-------- 8 files changed, 40 insertions(+), 28 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 77838e6c..441175a1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ on: pull_request: env: - GO_VERSION: 1.25.10 + GO_VERSION: 1.26.7 jobs: build-all: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 640fa62f..288bd482 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: - 'v*' env: - GO_VERSION: 1.25.10 + GO_VERSION: 1.26.7 jobs: release: diff --git a/.github/workflows/snapshots.yml b/.github/workflows/snapshots.yml index d96e8166..a30520ed 100644 --- a/.github/workflows/snapshots.yml +++ b/.github/workflows/snapshots.yml @@ -6,7 +6,7 @@ on: - cron: '0 2 * * *' # Every day at 2am env: - GO_VERSION: 1.25.10 + GO_VERSION: 1.26.7 jobs: snapshots: diff --git a/helm-java/src/test/java/com/marcnuri/helm/HelmPushTest.java b/helm-java/src/test/java/com/marcnuri/helm/HelmPushTest.java index 79f11637..907289de 100644 --- a/helm-java/src/test/java/com/marcnuri/helm/HelmPushTest.java +++ b/helm-java/src/test/java/com/marcnuri/helm/HelmPushTest.java @@ -61,7 +61,8 @@ void tearDown() { void pushUnauthorizedThrowsException() { final PushCommand pushCommand = Helm.push() .withChart(packagedChart) - .withRemote(URI.create("oci://" + remoteServer)); + .withRemote(URI.create("oci://" + remoteServer)) + .plainHttp(); assertThatIllegalStateException() .isThrownBy(pushCommand::call) .extracting(IllegalStateException::getMessage) @@ -74,6 +75,7 @@ void pushUnauthorizedWithDebugThrowsExceptionWithDetail() { final PushCommand pushCommand = Helm.push() .withChart(packagedChart) .withRemote(URI.create("oci://" + remoteServer)) + .plainHttp() .debug(); assertThatIllegalStateException() .isThrownBy(pushCommand::call) @@ -88,10 +90,11 @@ void pushUnauthorizedWithDebugThrowsExceptionWithDetail() { @Test void pushAuthorized() { - Helm.registry().login().withHost(remoteServer).withUsername("username").withPassword(password).call(); + Helm.registry().login().withHost(remoteServer).withUsername("username").withPassword(password).plainHttp().call(); final String result = Helm.push() .withChart(packagedChart) .withRemote(URI.create("oci://" + remoteServer)) + .plainHttp() .call(); assertThat(result) .contains("Pushed: ", "test:0.1.0", "Digest: "); @@ -99,10 +102,11 @@ void pushAuthorized() { @Test void pushWithDebugShowsDebugMessages() { - Helm.registry().login().withHost(remoteServer).withUsername("username").withPassword(password).call(); + Helm.registry().login().withHost(remoteServer).withUsername("username").withPassword(password).plainHttp().call(); final String result = Helm.push() .withChart(packagedChart) .withRemote(URI.create("oci://" + remoteServer)) + .plainHttp() .debug() .call(); assertThat(result) diff --git a/helm-java/src/test/java/com/marcnuri/helm/HelmRegistryTest.java b/helm-java/src/test/java/com/marcnuri/helm/HelmRegistryTest.java index 375e9016..9a439af2 100644 --- a/helm-java/src/test/java/com/marcnuri/helm/HelmRegistryTest.java +++ b/helm-java/src/test/java/com/marcnuri/helm/HelmRegistryTest.java @@ -43,7 +43,7 @@ class Login { @Test void withValidCredentialsSucceeds() { final String result = Helm.registry().login() - .withHost(remoteServer).withUsername("username").withPassword("password").call(); + .withHost(remoteServer).withUsername("username").withPassword("password").plainHttp().call(); assertThat(result).startsWith("Login Succeeded"); } @@ -51,7 +51,7 @@ void withValidCredentialsSucceeds() { void withDebugAndValidCredentialsSucceeds() { final String result = Helm.registry().login() .debug() - .withHost(remoteServer).withUsername("username").withPassword("password").call(); + .withHost(remoteServer).withUsername("username").withPassword("password").plainHttp().call(); assertThat(result) .containsPattern(Pattern.compile("^Login Succeeded$", Pattern.MULTILINE)) .contains("level=info msg=\"authorized request\""); @@ -60,7 +60,7 @@ void withDebugAndValidCredentialsSucceeds() { @Test void withInvalidCredentialsFails() { final RegistryCommand.LoginCommand loginCommand = Helm.registry().login() - .withHost(remoteServer).withUsername("username").withPassword("invalid"); + .withHost(remoteServer).withUsername("username").withPassword("invalid").plainHttp(); assertThatThrownBy(loginCommand::call) .isInstanceOf(IllegalStateException.class) .hasMessageContainingAll( @@ -72,7 +72,7 @@ void withInvalidCredentialsFails() { void withDebugAndInvalidCredentialsFails() { final RegistryCommand.LoginCommand loginCommand = Helm.registry().login() .debug() - .withHost(remoteServer).withUsername("username").withPassword("invalid"); + .withHost(remoteServer).withUsername("username").withPassword("invalid").plainHttp(); assertThatThrownBy(loginCommand::call) .isInstanceOf(IllegalStateException.class) .hasMessageContainingAll( @@ -87,7 +87,7 @@ class Logout { @Test void withPreviousLoginSucceeds() { - Helm.registry().login().withHost(remoteServer).withUsername("username").withPassword("password").call(); + Helm.registry().login().withHost(remoteServer).withUsername("username").withPassword("password").plainHttp().call(); final String result = Helm.registry().logout() .withHost(remoteServer).call(); assertThat(result).startsWith("Removing login credentials for " + remoteServer); @@ -95,7 +95,7 @@ void withPreviousLoginSucceeds() { @Test void withDebugAndPreviousLoginSucceeds() { - Helm.registry().login().withHost(remoteServer).withUsername("username").withPassword("password").call(); + Helm.registry().login().withHost(remoteServer).withUsername("username").withPassword("password").plainHttp().call(); final String result = Helm.registry().logout() .debug() .withHost(remoteServer).call(); diff --git a/helm-java/src/test/java/com/marcnuri/helm/HelmShowTest.java b/helm-java/src/test/java/com/marcnuri/helm/HelmShowTest.java index 280567e0..3d7afff1 100644 --- a/helm-java/src/test/java/com/marcnuri/helm/HelmShowTest.java +++ b/helm-java/src/test/java/com/marcnuri/helm/HelmShowTest.java @@ -137,12 +137,13 @@ void setUp() { final String password = UUID.randomUUID().toString(); // If default password is used, test is flaky ¯\_(ツ)_/¯ remoteServer = Helm.HelmLibHolder.INSTANCE.RepoOciServerStart( new RepoServerOptions(null, null, password)).out; - Helm.registry().login().withHost(remoteServer).withUsername("username").withPassword(password).call(); + Helm.registry().login().withHost(remoteServer).withUsername("username").withPassword(password).plainHttp().call(); helm.packageIt().withDestination(tempDir).call(); final Path packagedChart = tempDir.resolve("test-0.1.0.tgz"); Helm.push() .withChart(packagedChart) .withRemote(URI.create("oci://" + remoteServer)) + .plainHttp() .call(); } @@ -172,12 +173,13 @@ void setUp() { final String password = UUID.randomUUID().toString(); // If default password is used, test is flaky ¯\_(ツ)_/¯ remoteServer = Helm.HelmLibHolder.INSTANCE.RepoOciServerStart( new RepoServerOptions(null, null, password)).out; - Helm.registry().login().withHost(remoteServer).withUsername("username").withPassword(password).call(); + Helm.registry().login().withHost(remoteServer).withUsername("username").withPassword(password).plainHttp().call(); helm.packageIt().withDestination(tempDir).call(); final Path packagedChart = tempDir.resolve("test-0.1.0.tgz"); Helm.push() .withChart(packagedChart) .withRemote(URI.create("oci://" + remoteServer)) + .plainHttp() .call(); } diff --git a/native/internal/helm/registry.go b/native/internal/helm/registry.go index e6518b20..44057af4 100644 --- a/native/internal/helm/registry.go +++ b/native/internal/helm/registry.go @@ -60,6 +60,7 @@ func RegistryLogin(options *RegistryOptions) (string, error) { action.WithKeyFile(options.KeyFile), action.WithCAFile(options.CaFile), action.WithInsecure(options.InsecureSkipTLSverify), + action.WithPlainHTTPLogin(options.PlainHttp), ) return appendToOutOrErr(debugBuffer, getRegistryClientOut().String(), err) } diff --git a/native/main_test.go b/native/main_test.go index 9d840a2b..743f430e 100644 --- a/native/main_test.go +++ b/native/main_test.go @@ -166,15 +166,17 @@ func TestPush(t *testing.T) { }) _ = helm.Package(&helm.PackageOptions{Path: create, Destination: dir}) _, _ = helm.RegistryLogin(&helm.RegistryOptions{ - Hostname: srv.RegistryURL, - Username: "username", - Password: "password", - Debug: true, + Hostname: srv.RegistryURL, + Username: "username", + Password: "password", + Debug: true, + CertOptions: helm.CertOptions{PlainHttp: true}, }) out, err := helm.Push(&helm.PushOptions{ - Chart: path.Join(dir, "test-0.1.0.tgz"), - Remote: "oci://" + srv.RegistryURL, - Debug: true, + Chart: path.Join(dir, "test-0.1.0.tgz"), + Remote: "oci://" + srv.RegistryURL, + Debug: true, + CertOptions: helm.CertOptions{PlainHttp: true}, }) if err != nil { t.Errorf("Expected push to succeed, got %s", err) @@ -217,9 +219,10 @@ func TestRegistryLogin(t *testing.T) { t.Errorf("Expected server to be started") } _, err = helm.RegistryLogin(&helm.RegistryOptions{ - Hostname: srv.RegistryURL, - Username: "username", - Password: "password", + Hostname: srv.RegistryURL, + Username: "username", + Password: "password", + CertOptions: helm.CertOptions{PlainHttp: true}, }) if err != nil { t.Errorf("Expected login to succeed, got %s", err) @@ -246,16 +249,18 @@ func TestRegistryLogout(t *testing.T) { defer helm.RepoServerStopAll() srv, _ := helm.RepoOciServerStart(&helm.RepoServerOptions{}) _, err := helm.RegistryLogin(&helm.RegistryOptions{ - Hostname: srv.RegistryURL, - Username: "username", - Password: "password", + Hostname: srv.RegistryURL, + Username: "username", + Password: "password", + CertOptions: helm.CertOptions{PlainHttp: true}, }) if err != nil { t.Error("Expected initial login to succeed") } var out string out, err = helm.RegistryLogout(&helm.RegistryOptions{ - Hostname: srv.RegistryURL, + Hostname: srv.RegistryURL, + CertOptions: helm.CertOptions{PlainHttp: true}, }) if err != nil { t.Errorf("Expected logout to succeed, got %s", err)