diff --git a/docs/3-create-a-zarf-package/2-zarf-components.md b/docs/3-create-a-zarf-package/2-zarf-components.md index 1ccd87dcc1..8b5d584954 100644 --- a/docs/3-create-a-zarf-package/2-zarf-components.md +++ b/docs/3-create-a-zarf-package/2-zarf-components.md @@ -68,6 +68,12 @@ Charts using the `url` key can be: - A remote URL (oci://) to an OCI registry - A remote URL (http/https) to a Helm repository +:::note + +To use a private Helm repository the repo must be added to Helm. You can add a repo to Helm with the [`helm repo add`](https://helm.sh/docs/helm/helm_repo_add/) command or the internal [`zarf tools helm repo add`](../2-the-zarf-cli/100-cli-commands/zarf_tools_helm_repo_add.md) command. + +::: + #### Chart Examples diff --git a/examples/helm-charts/zarf.yaml b/examples/helm-charts/zarf.yaml index 27a50c5595..e43d87beef 100644 --- a/examples/helm-charts/zarf.yaml +++ b/examples/helm-charts/zarf.yaml @@ -16,7 +16,7 @@ components: localPath: chart valuesFiles: - values.yaml - + - name: podinfo-oci version: 6.4.0 namespace: podinfo-from-oci diff --git a/src/internal/packager/helm/repo.go b/src/internal/packager/helm/repo.go index ae41d2b0bc..fe0423326d 100644 --- a/src/internal/packager/helm/repo.go +++ b/src/internal/packager/helm/repo.go @@ -143,6 +143,15 @@ func (h *Helm) DownloadPublishedChart(cosignKeyPath string) error { chartURL string err error ) + repoFile, err := repo.LoadFile(pull.Settings.RepositoryConfig) + + // Not returning the error here since the repo file is only needed if we are pulling from a repo that requires authentication + if err != nil { + message.Debugf("Unable to load the repo file at %q: %s", pull.Settings.RepositoryConfig, err.Error()) + } + + var username string + var password string // Handle OCI registries if registry.IsOCI(h.chart.URL) { @@ -159,8 +168,18 @@ func (h *Helm) DownloadPublishedChart(cosignKeyPath string) error { chartName = h.chart.RepoName } - // Perform simple chart download - chartURL, err = repo.FindChartInRepoURL(h.chart.URL, chartName, h.chart.Version, pull.CertFile, pull.KeyFile, pull.CaFile, getter.All(pull.Settings)) + if repoFile != nil { + // TODO: @AustinAbro321 Currently this selects the last repo with the same url + // We should introduce a new field in zarf to allow users to specify the local repo they want + for _, repo := range repoFile.Repositories { + if repo.URL == h.chart.URL { + username = repo.Username + password = repo.Password + } + } + } + + chartURL, err = repo.FindChartInAuthRepoURL(h.chart.URL, username, password, chartName, h.chart.Version, pull.CertFile, pull.KeyFile, pull.CaFile, getter.All(pull.Settings)) if err != nil { if strings.Contains(err.Error(), "not found") { // Intentionally dogsled this error since this is just a nice to have helper @@ -179,6 +198,7 @@ func (h *Helm) DownloadPublishedChart(cosignKeyPath string) error { Getters: getter.All(pull.Settings), Options: []getter.Option{ getter.WithInsecureSkipVerifyTLS(config.CommonOptions.Insecure), + getter.WithBasicAuth(username, password), }, } diff --git a/src/test/external/README.md b/src/test/external/README.md index d6dbbe1bc3..11ca520411 100644 --- a/src/test/external/README.md +++ b/src/test/external/README.md @@ -25,5 +25,5 @@ make test-external ``` bash # If you are in the root folder of the repository and already have everything built (i.e., the binary, the init-package and the flux-test example package): -go test ./src/test/external-test/... +go test ./src/test/external/... -v ``` diff --git a/src/test/external/ext_out_cluster_test.go b/src/test/external/ext_out_cluster_test.go index 92be12ffe1..89f2d9d0aa 100644 --- a/src/test/external/ext_out_cluster_test.go +++ b/src/test/external/ext_out_cluster_test.go @@ -5,33 +5,43 @@ package external import ( + "bytes" + "encoding/json" "fmt" "io" + "mime/multipart" "net/http" + "os" + "path/filepath" "testing" + "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/pkg/utils/exec" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "helm.sh/helm/v3/pkg/repo" ) // Docker/k3d networking constants const ( - network = "k3d-k3s-external-test" - subnet = "172.31.0.0/16" - gateway = "172.31.0.1" - giteaIP = "172.31.0.99" - giteaHost = "gitea.localhost" - registryHost = "registry.localhost" - clusterName = "zarf-external-test" + network = "k3d-k3s-external-test" + subnet = "172.31.0.0/16" + gateway = "172.31.0.1" + giteaIP = "172.31.0.99" + giteaHost = "gitea.localhost" + registryHost = "registry.localhost" + clusterName = "zarf-external-test" + giteaUser = "git-user" + registryUser = "push-user" + commonPassword = "superSecurePassword" ) var outClusterCredentialArgs = []string{ - "--git-push-username=git-user", - "--git-push-password=superSecurePassword", + "--git-push-username=" + giteaUser, + "--git-push-password=" + commonPassword, "--git-url=http://" + giteaHost + ":3000", - "--registry-push-username=push-user", - "--registry-push-password=superSecurePassword", + "--registry-push-username=" + registryUser, + "--registry-push-password=" + commonPassword, "--registry-url=k3d-" + registryHost + ":5000"} type ExtOutClusterTestSuite struct { @@ -40,6 +50,7 @@ type ExtOutClusterTestSuite struct { } func (suite *ExtOutClusterTestSuite) SetupSuite() { + suite.Assertions = require.New(suite.T()) // Teardown any leftovers from previous tests @@ -97,7 +108,7 @@ func (suite *ExtOutClusterTestSuite) Test_0_Mirror() { suite.NoError(err, "unable to mirror the package with zarf") // Check that the registry contains the images we want - regCatalogURL := fmt.Sprintf("http://push-user:superSecurePassword@k3d-%s:5000/v2/_catalog", registryHost) + regCatalogURL := fmt.Sprintf("http://%s:%s@k3d-%s:5000/v2/_catalog", registryUser, commonPassword, registryHost) respReg, err := http.Get(regCatalogURL) suite.NoError(err) regBody, err := io.ReadAll(respReg.Body) @@ -107,7 +118,7 @@ func (suite *ExtOutClusterTestSuite) Test_0_Mirror() { suite.Contains(string(regBody), "stefanprodan/podinfo", "registry did not contain the expected image") // Check that the git server contains the repos we want - gitRepoURL := fmt.Sprintf("http://git-user:superSecurePassword@%s:3000/api/v1/repos/search", giteaHost) + gitRepoURL := fmt.Sprintf("http://%s:%s@%s:3000/api/v1/repos/search", giteaUser, commonPassword, giteaHost) respGit, err := http.Get(gitRepoURL) suite.NoError(err) gitBody, err := io.ReadAll(respGit.Body) @@ -136,6 +147,104 @@ func (suite *ExtOutClusterTestSuite) Test_1_Deploy() { suite.True(success, errorStr) } +func (suite *ExtOutClusterTestSuite) Test_2_AuthToPrivateHelmChart() { + baseURL := fmt.Sprintf("http://%s:3000", giteaHost) + + suite.createHelmChartInGitea(baseURL, giteaUser, commonPassword) + suite.makeGiteaUserPrivate(baseURL, giteaUser, commonPassword) + + tempDir := suite.T().TempDir() + repoPath := filepath.Join(tempDir, "repositories.yaml") + os.Setenv("HELM_REPOSITORY_CONFIG", repoPath) + defer os.Unsetenv("HELM_REPOSITORY_CONFIG") + + packagePath := filepath.Join("..", "packages", "external-helm-auth") + findImageArgs := []string{"dev", "find-images", packagePath} + err := exec.CmdWithPrint(zarfBinPath, findImageArgs...) + suite.Error(err, "Since auth has not been setup, this should fail") + + repoFile := repo.NewFile() + + chartURL := fmt.Sprintf("%s/api/packages/%s/helm", baseURL, giteaUser) + entry := &repo.Entry{ + Name: "temp_entry", + Username: giteaUser, + Password: commonPassword, + URL: chartURL, + } + repoFile.Add(entry) + utils.WriteYaml(repoPath, repoFile, 0600) + + err = exec.CmdWithPrint(zarfBinPath, findImageArgs...) + suite.NoError(err, "Unable to find images, helm auth likely failed") + + packageCreateArgs := []string{"package", "create", packagePath, fmt.Sprintf("--output=%s", tempDir), "--confirm"} + err = exec.CmdWithPrint(zarfBinPath, packageCreateArgs...) + suite.NoError(err, "Unable to create package, helm auth likely failed") +} + +func (suite *ExtOutClusterTestSuite) createHelmChartInGitea(baseURL string, username string, password string) { + tempDir := suite.T().TempDir() + podInfoVersion := "6.4.0" + podinfoChartPath := filepath.Join("..", "..", "..", "examples", "helm-charts", "chart") + err := exec.CmdWithPrint("helm", "package", podinfoChartPath, "--destination", tempDir) + podinfoTarballPath := filepath.Join(tempDir, fmt.Sprintf("podinfo-%s.tgz", podInfoVersion)) + suite.NoError(err, "Unable to package chart") + + utils.DownloadToFile(fmt.Sprintf("https://stefanprodan.github.io/podinfo/podinfo-%s.tgz", podInfoVersion), podinfoTarballPath, "") + url := fmt.Sprintf("%s/api/packages/%s/helm/api/charts", baseURL, username) + + file, err := os.Open(podinfoTarballPath) + suite.NoError(err) + defer file.Close() + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("file", podinfoTarballPath) + suite.NoError(err) + _, err = io.Copy(part, file) + suite.NoError(err) + writer.Close() + + req, err := http.NewRequest("POST", url, body) + suite.NoError(err) + + req.Header.Set("Content-Type", writer.FormDataContentType()) + req.SetBasicAuth(username, password) + + client := &http.Client{} + + resp, err := client.Do(req) + suite.NoError(err) + resp.Body.Close() +} + +func (suite *ExtOutClusterTestSuite) makeGiteaUserPrivate(baseURL string, username string, password string) { + url := fmt.Sprintf("%s/api/v1/admin/users/%s", baseURL, username) + + userOption := map[string]interface{}{ + "visibility": "private", + "login_name": username, + } + + jsonData, err := json.Marshal(userOption) + suite.NoError(err) + + req, err := http.NewRequest(http.MethodPatch, url, bytes.NewBuffer(jsonData)) + suite.NoError(err) + + req.Header.Set("Content-Type", "application/json") + req.SetBasicAuth(username, password) + + client := &http.Client{} + resp, err := client.Do(req) + suite.NoError(err) + defer resp.Body.Close() + + _, err = io.ReadAll(resp.Body) + suite.NoError(err) +} + func TestExtOurClusterTestSuite(t *testing.T) { suite.Run(t, new(ExtOutClusterTestSuite)) } diff --git a/src/test/packages/external-helm-auth/zarf.yaml b/src/test/packages/external-helm-auth/zarf.yaml new file mode 100644 index 0000000000..d373670825 --- /dev/null +++ b/src/test/packages/external-helm-auth/zarf.yaml @@ -0,0 +1,14 @@ +kind: ZarfPackageConfig +metadata: + name: helm-auth + version: 0.0.1 +components: +- name: private-chart + required: true + charts: + - name: podinfo + version: 6.4.0 + url: http://gitea.localhost:3000/api/packages/git-user/helm + repoName: podinfo + namespace: podinfo-from-repo + releaseName: cool-release-name