Skip to content

Commit c549dc8

Browse files
authored
Merge branch 'release/2.0' into mergify/bp/release/2.0/pr-705
2 parents dbe0e37 + f190939 commit c549dc8

7 files changed

Lines changed: 179 additions & 6 deletions

File tree

.github/workflows/tests.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,18 +139,18 @@ jobs:
139139
# verify via helm repository
140140
hauler store add chart rancher --repo https://releases.rancher.com/server-charts/stable
141141
hauler store add chart rancher --repo https://releases.rancher.com/server-charts/stable --version 2.8.4
142-
hauler store add chart rancher --repo https://releases.rancher.com/server-charts/stable --version 2.8.3 --verify
143142
# verify via oci helm repository
144143
hauler store add chart hauler-helm --repo oci://ghcr.io/hauler-dev
145144
hauler store add chart hauler-helm --repo oci://ghcr.io/hauler-dev --version 1.0.6
146-
hauler store add chart hauler-helm --repo oci://ghcr.io/hauler-dev --version 1.0.4 --verify
147145
# verify via local helm repository
148146
curl -sfOL https://github.com/rancherfederal/rancher-cluster-templates/releases/download/rancher-cluster-templates-0.5.2/rancher-cluster-templates-0.5.2.tgz
149147
hauler store add chart rancher-cluster-templates-0.5.2.tgz --repo .
150148
curl -sfOL https://github.com/rancherfederal/rancher-cluster-templates/releases/download/rancher-cluster-templates-0.5.1/rancher-cluster-templates-0.5.1.tgz
151149
hauler store add chart rancher-cluster-templates-0.5.1.tgz --repo . --version 0.5.1
152-
curl -sfOL https://github.com/rancherfederal/rancher-cluster-templates/releases/download/rancher-cluster-templates-0.5.0/rancher-cluster-templates-0.5.0.tgz
153-
hauler store add chart rancher-cluster-templates-0.5.0.tgz --repo . --version 0.5.0 --verify
150+
curl -sfLO https://raw.githubusercontent.com/helm/helm/v3.18.6/cmd/helm/testdata/testcharts/signtest-0.1.0.tgz
151+
curl -sfLO https://raw.githubusercontent.com/helm/helm/v3.18.6/cmd/helm/testdata/testcharts/signtest-0.1.0.tgz.prov
152+
curl -sfLO https://raw.githubusercontent.com/helm/helm/v3.18.6/cmd/helm/testdata/helm-test-key.pub
153+
hauler store add chart signtest-0.1.0.tgz --repo . --version 0.1.0 --verify --keyring helm-test-key.pub
154154
# verify via the hauler store contents
155155
hauler store info
156156

cmd/hauler/cli/store/sync.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -501,11 +501,26 @@ func processContent(ctx context.Context, fi *os.File, o *flags.SyncOpts, s *stor
501501
valuesFiles = append(valuesFiles, filepath.Join(filepath.Dir(fi.Name()), path))
502502
}
503503

504+
chartUsername, chartPassword, err := resolveChartCreds(ch)
505+
if err != nil {
506+
return err
507+
}
508+
504509
if err := storeChart(ctx, s, ch,
505510
&flags.AddChartOpts{
506511
ChartOpts: &action.ChartPathOptions{
507-
RepoURL: ch.RepoURL,
508-
Version: ch.Version,
512+
RepoURL: ch.RepoURL,
513+
Version: ch.Version,
514+
Verify: ch.Verify,
515+
Keyring: ch.Keyring,
516+
Username: chartUsername,
517+
Password: chartPassword,
518+
PassCredentialsAll: ch.PassCredentialsAll,
519+
CertFile: ch.CertFile,
520+
KeyFile: ch.KeyFile,
521+
CaFile: ch.CaFile,
522+
InsecureSkipTLSVerify: ch.InsecureSkipTLSVerify,
523+
PlainHTTP: ch.PlainHTTP,
509524
},
510525
AddImages: ch.AddImages,
511526
AddDependencies: ch.AddDependencies,
@@ -532,6 +547,25 @@ func processContent(ctx context.Context, fi *os.File, o *flags.SyncOpts, s *stor
532547
return nil
533548
}
534549

550+
// resolveChartCreds reads credentials for a Chart entry from the env vars
551+
// named by UsernameEnv and PasswordEnv. Both fields must be set or both must
552+
// be empty; a mix is a configuration error. If both are set, the env vars
553+
// must be non-empty at runtime.
554+
func resolveChartCreds(ch v1.Chart) (username, password string, err error) {
555+
if ch.UsernameEnv == "" && ch.PasswordEnv == "" {
556+
return "", "", nil
557+
}
558+
if ch.UsernameEnv == "" || ch.PasswordEnv == "" {
559+
return "", "", fmt.Errorf("chart %q: usernameEnv and passwordEnv must both be set or both be empty", ch.Name)
560+
}
561+
username = os.Getenv(ch.UsernameEnv)
562+
password = os.Getenv(ch.PasswordEnv)
563+
if username == "" || password == "" {
564+
return "", "", fmt.Errorf("chart %q: env vars %q and %q must both be set and non-empty", ch.Name, ch.UsernameEnv, ch.PasswordEnv)
565+
}
566+
return username, password, nil
567+
}
568+
535569
func processImageTxt(ctx context.Context, fi *os.File, o *flags.SyncOpts, s *store.Layout, rso *flags.StoreRootOpts, ro *flags.CliRootOpts) error {
536570
l := log.FromContext(ctx)
537571
l.Infof("syncing images from [%s] to store", filepath.Base(fi.Name()))

cmd/hauler/cli/store/sync_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/rs/zerolog"
2121

2222
"hauler.dev/go/hauler/v2/internal/flags"
23+
v1 "hauler.dev/go/hauler/v2/pkg/apis/hauler.cattle.io/v1"
2324
"hauler.dev/go/hauler/v2/pkg/consts"
2425
)
2526

@@ -49,6 +50,95 @@ func newSyncOpts(storeDir string) *flags.SyncOpts {
4950
}
5051
}
5152

53+
// --------------------------------------------------------------------------
54+
// resolveChartCreds tests
55+
// --------------------------------------------------------------------------
56+
57+
func TestResolveChartCreds_BothEmpty(t *testing.T) {
58+
ch := v1.Chart{Name: "mychart", RepoURL: "https://charts.example.com"}
59+
u, p, err := resolveChartCreds(ch)
60+
if err != nil {
61+
t.Fatalf("unexpected error: %v", err)
62+
}
63+
if u != "" || p != "" {
64+
t.Errorf("expected empty creds, got username=%q password=%q", u, p)
65+
}
66+
}
67+
68+
func TestResolveChartCreds_BothSetAndEnvPopulated(t *testing.T) {
69+
t.Setenv("CHART_TEST_USER", "alice")
70+
t.Setenv("CHART_TEST_PASS", "s3cr3t")
71+
72+
ch := v1.Chart{
73+
Name: "mychart",
74+
RepoURL: "https://charts.example.com",
75+
UsernameEnv: "CHART_TEST_USER",
76+
PasswordEnv: "CHART_TEST_PASS",
77+
}
78+
u, p, err := resolveChartCreds(ch)
79+
if err != nil {
80+
t.Fatalf("unexpected error: %v", err)
81+
}
82+
if u != "alice" {
83+
t.Errorf("username: got %q, want %q", u, "alice")
84+
}
85+
if p != "s3cr3t" {
86+
t.Errorf("password: got %q, want %q", p, "s3cr3t")
87+
}
88+
}
89+
90+
func TestResolveChartCreds_OnlyUsernameEnvSet_ReturnsError(t *testing.T) {
91+
ch := v1.Chart{
92+
Name: "mychart",
93+
RepoURL: "https://charts.example.com",
94+
UsernameEnv: "CHART_TEST_USER_ONLY",
95+
// PasswordEnv intentionally omitted
96+
}
97+
_, _, err := resolveChartCreds(ch)
98+
if err == nil {
99+
t.Fatal("expected error when only usernameEnv is set, got nil")
100+
}
101+
if !strings.Contains(err.Error(), "usernameEnv and passwordEnv must both be set") {
102+
t.Errorf("unexpected error message: %v", err)
103+
}
104+
}
105+
106+
func TestResolveChartCreds_OnlyPasswordEnvSet_ReturnsError(t *testing.T) {
107+
ch := v1.Chart{
108+
Name: "mychart",
109+
RepoURL: "https://charts.example.com",
110+
// UsernameEnv intentionally omitted
111+
PasswordEnv: "CHART_TEST_PASS_ONLY",
112+
}
113+
_, _, err := resolveChartCreds(ch)
114+
if err == nil {
115+
t.Fatal("expected error when only passwordEnv is set, got nil")
116+
}
117+
if !strings.Contains(err.Error(), "usernameEnv and passwordEnv must both be set") {
118+
t.Errorf("unexpected error message: %v", err)
119+
}
120+
}
121+
122+
func TestResolveChartCreds_EnvVarUnset_ReturnsError(t *testing.T) {
123+
// Ensure the env vars are definitely absent.
124+
t.Setenv("CHART_UNSET_USER", "")
125+
t.Setenv("CHART_UNSET_PASS", "")
126+
127+
ch := v1.Chart{
128+
Name: "mychart",
129+
RepoURL: "https://charts.example.com",
130+
UsernameEnv: "CHART_UNSET_USER",
131+
PasswordEnv: "CHART_UNSET_PASS",
132+
}
133+
_, _, err := resolveChartCreds(ch)
134+
if err == nil {
135+
t.Fatal("expected error when env vars are empty, got nil")
136+
}
137+
if !strings.Contains(err.Error(), "must both be set and non-empty") {
138+
t.Errorf("unexpected error message: %v", err)
139+
}
140+
}
141+
52142
// --------------------------------------------------------------------------
53143
// processContent tests
54144
// --------------------------------------------------------------------------

internal/flags/add.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ func (o *AddChartOpts) AddFlags(cmd *cobra.Command) {
6666
f.StringVar(&o.ChartOpts.RepoURL, "repo", "", "Location of the chart (https:// | http:// | oci://)")
6767
f.StringVar(&o.ChartOpts.Version, "version", "", "(Optional) Specify the version of the chart (v1.0.0 | 2.0.0 | ^2.0.0)")
6868
f.BoolVar(&o.ChartOpts.Verify, "verify", false, "(Optional) Verify the chart before fetching it")
69+
f.StringVar(&o.ChartOpts.Keyring, "keyring", "", "(Optional) Location of public keyring used by --verify (default: $HOME/.gnupg/pubring.gpg)")
6970
f.StringVar(&o.ChartOpts.Username, "username", "", "(Optional) Username to use for authentication")
7071
f.StringVar(&o.ChartOpts.Password, "password", "", "(Optional) Password to use for authentication")
7172
f.StringVar(&o.ChartOpts.CertFile, "cert-file", "", "(Optional) Location of the TLS Certificate to use for client authentication")

pkg/apis/hauler.cattle.io/v1/chart.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,21 @@ type Chart struct {
2525
AddImages bool `json:"add-images,omitempty"`
2626
AddDependencies bool `json:"add-dependencies,omitempty"`
2727
ExcludeExtras bool `json:"exclude-extras,omitempty"`
28+
29+
// Verification
30+
Verify bool `json:"verify,omitempty"`
31+
Keyring string `json:"keyring,omitempty"`
32+
33+
// Auth (HTTP repos only — for OCI registries use `hauler login`)
34+
// Credentials are referenced by env-var name; raw values must NOT appear in manifests.
35+
UsernameEnv string `json:"usernameEnv,omitempty"`
36+
PasswordEnv string `json:"passwordEnv,omitempty"`
37+
PassCredentialsAll bool `json:"passCredentialsAll,omitempty"`
38+
39+
// TLS
40+
CertFile string `json:"certFile,omitempty"`
41+
KeyFile string `json:"keyFile,omitempty"`
42+
CaFile string `json:"caFile,omitempty"`
43+
InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`
44+
PlainHTTP bool `json:"plainHTTP,omitempty"`
2845
}

pkg/content/chart/chart.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,21 @@ func NewChart(name string, opts *action.ChartPathOptions) (*Chart, error) {
5050
}
5151

5252
client := action.NewInstall(actionConfig)
53+
54+
// Propagate auth, TLS, and verification options from the caller.
55+
// RepoURL is intentionally NOT copied here — it is set conditionally below
56+
// based on URL scheme (OCI vs HTTP vs bare).
5357
client.ChartPathOptions.Version = opts.Version
58+
client.ChartPathOptions.Verify = opts.Verify
59+
client.ChartPathOptions.Keyring = opts.Keyring
60+
client.ChartPathOptions.Username = opts.Username
61+
client.ChartPathOptions.Password = opts.Password
62+
client.ChartPathOptions.PassCredentialsAll = opts.PassCredentialsAll
63+
client.ChartPathOptions.CertFile = opts.CertFile
64+
client.ChartPathOptions.KeyFile = opts.KeyFile
65+
client.ChartPathOptions.CaFile = opts.CaFile
66+
client.ChartPathOptions.InsecureSkipTLSVerify = opts.InsecureSkipTLSVerify
67+
client.ChartPathOptions.PlainHTTP = opts.PlainHTTP
5468

5569
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
5670
client.InsecureSkipTLSVerify, client.PlainHTTP)

pkg/content/chart/chart_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package chart_test
33
import (
44
"os"
55
"reflect"
6+
"strings"
67
"testing"
78

89
v1 "github.com/google/go-containerregistry/pkg/v1"
@@ -129,3 +130,19 @@ func TestNewChart(t *testing.T) {
129130
})
130131
}
131132
}
133+
134+
func TestNewChart_VerifyOnUnsignedChartFails(t *testing.T) {
135+
_, err := chart.NewChart(
136+
"rancher-cluster-templates-0.5.2.tgz",
137+
&action.ChartPathOptions{
138+
RepoURL: "../../../testdata",
139+
Verify: true,
140+
},
141+
)
142+
if err == nil {
143+
t.Fatalf("expected verify failure on unsigned chart, got nil error")
144+
}
145+
if !strings.Contains(err.Error(), "provenance") {
146+
t.Fatalf("expected provenance error, got: %v", err)
147+
}
148+
}

0 commit comments

Comments
 (0)