-
Notifications
You must be signed in to change notification settings - Fork 700
HELM-703: Add authentication for Helm chart URLs #16360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
12f04cf
03eaa6c
d364f9e
cba6e35
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -65,38 +65,48 @@ const HelmURLChartInstallPage: FunctionComponent = () => { | |||||||||||
| chartURL: '', | ||||||||||||
| chartVersion: '', | ||||||||||||
| namespace, | ||||||||||||
| basicAuthSecretName: '', | ||||||||||||
| }; | ||||||||||||
|
|
||||||||||||
| const fetchChartData = useCallback(async (chartURL: string, chartVersion: string) => { | ||||||||||||
| setIsLoadingChart(true); | ||||||||||||
| setChartError(null); | ||||||||||||
| const fetchChartData = useCallback( | ||||||||||||
| async (chartURL: string, chartVersion: string, basicAuthSecretName: string) => { | ||||||||||||
| setIsLoadingChart(true); | ||||||||||||
| setChartError(null); | ||||||||||||
|
|
||||||||||||
| try { | ||||||||||||
| const fullChartURL = getFullChartURL(chartURL, chartVersion); | ||||||||||||
| const apiUrl = `/api/helm/chart?url=${encodeURIComponent(fullChartURL)}&noRepo=true`; | ||||||||||||
| try { | ||||||||||||
| const fullChartURL = getFullChartURL(chartURL, chartVersion); | ||||||||||||
| let authParam = ''; | ||||||||||||
| if (basicAuthSecretName) { | ||||||||||||
| authParam = `&basic_auth_secret_name=${encodeURIComponent(basicAuthSecretName)}`; | ||||||||||||
| } | ||||||||||||
|
Comment on lines
+78
to
+81
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alternatively, I believe you can use a ternary,
Suggested change
which avoids the need for using |
||||||||||||
| const apiUrl = `/api/helm/chart?url=${encodeURIComponent( | ||||||||||||
| fullChartURL, | ||||||||||||
| )}&noRepo=true&namespace=${namespace}${authParam}`; | ||||||||||||
|
Comment on lines
+82
to
+84
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might be prettier if you went ahead and used one more local variable:
Suggested change
|
||||||||||||
|
|
||||||||||||
| const res = await coFetchJSON(apiUrl); | ||||||||||||
| const chart: HelmChart = res?.chart || res; | ||||||||||||
| const valuesYAML = getChartValuesYAML(chart); | ||||||||||||
| const valuesJSON = chart?.values ?? {}; | ||||||||||||
| const valuesSchema = chart?.schema && JSON.parse(atob(chart?.schema)); | ||||||||||||
| const res = await coFetchJSON(apiUrl); | ||||||||||||
| const chart: HelmChart = res?.chart || res; | ||||||||||||
| const valuesYAML = getChartValuesYAML(chart); | ||||||||||||
| const valuesJSON = chart?.values ?? {}; | ||||||||||||
| const valuesSchema = chart?.schema && JSON.parse(atob(chart?.schema)); | ||||||||||||
|
|
||||||||||||
| setInitialYamlData(valuesYAML); | ||||||||||||
| setInitialFormData(valuesJSON as Record<string, unknown>); | ||||||||||||
| setInitialFormSchema(valuesSchema); | ||||||||||||
| setChartHasValues(!!valuesYAML); | ||||||||||||
| setChartData(chart); | ||||||||||||
| } catch (e) { | ||||||||||||
| setChartError(e as Error); | ||||||||||||
| } finally { | ||||||||||||
| setIsLoadingChart(false); | ||||||||||||
| } | ||||||||||||
| }, []); | ||||||||||||
| setInitialYamlData(valuesYAML); | ||||||||||||
| setInitialFormData(valuesJSON as Record<string, unknown>); | ||||||||||||
| setInitialFormSchema(valuesSchema); | ||||||||||||
| setChartHasValues(!!valuesYAML); | ||||||||||||
| setChartData(chart); | ||||||||||||
| } catch (e) { | ||||||||||||
| setChartError(e as Error); | ||||||||||||
| } finally { | ||||||||||||
| setIsLoadingChart(false); | ||||||||||||
| } | ||||||||||||
| }, | ||||||||||||
| [namespace], | ||||||||||||
| ); | ||||||||||||
|
|
||||||||||||
| const handleNextStep = useCallback( | ||||||||||||
| (values: HelmURLChartFormData) => { | ||||||||||||
| setChartDetails(values); | ||||||||||||
| fetchChartData(values.chartURL, values.chartVersion); | ||||||||||||
| fetchChartData(values.chartURL, values.chartVersion, values.basicAuthSecretName); | ||||||||||||
| setCurrentStep(WizardStep.ConfigureInstall); | ||||||||||||
| }, | ||||||||||||
| [fetchChartData], | ||||||||||||
|
|
@@ -112,7 +122,15 @@ const HelmURLChartInstallPage: FunctionComponent = () => { | |||||||||||
| values: HelmURLInstallFormData, | ||||||||||||
| actions: FormikHelpers<HelmURLInstallFormData>, | ||||||||||||
| ) => { | ||||||||||||
| const { releaseName, chartURL, chartVersion, yamlData, formData, editorType } = values; | ||||||||||||
| const { | ||||||||||||
| releaseName, | ||||||||||||
| chartURL, | ||||||||||||
| chartVersion, | ||||||||||||
| yamlData, | ||||||||||||
| formData, | ||||||||||||
| editorType, | ||||||||||||
| basicAuthSecretName, | ||||||||||||
| } = values; | ||||||||||||
|
|
||||||||||||
| let valuesObj: Record<string, unknown> | undefined; | ||||||||||||
| if (editorType === EditorType.Form) { | ||||||||||||
|
|
@@ -153,6 +171,7 @@ const HelmURLChartInstallPage: FunctionComponent = () => { | |||||||||||
| chart_url: fullChartURL, // eslint-disable-line @typescript-eslint/naming-convention | ||||||||||||
| ...(chartVersion ? { chart_version: chartVersion } : {}), // eslint-disable-line @typescript-eslint/naming-convention | ||||||||||||
| ...(valuesObj ? { values: valuesObj } : {}), | ||||||||||||
| ...(basicAuthSecretName ? { basic_auth_secret_name: basicAuthSecretName } : {}), // eslint-disable-line @typescript-eslint/naming-convention | ||||||||||||
| noRepo: true, | ||||||||||||
| }; | ||||||||||||
|
|
||||||||||||
|
|
@@ -197,6 +216,7 @@ const HelmURLChartInstallPage: FunctionComponent = () => { | |||||||||||
| chartURL: chartDetails?.chartURL || '', | ||||||||||||
| chartVersion: chartDetails?.chartVersion || '', | ||||||||||||
| namespace, | ||||||||||||
| basicAuthSecretName: chartDetails?.basicAuthSecretName || '', | ||||||||||||
| chartName: chartData?.metadata?.name || '', | ||||||||||||
| appVersion: chartData?.metadata?.appVersion || '', | ||||||||||||
| chartReadme: getChartReadme(chartData), | ||||||||||||
|
|
||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These changes seem very similar to the ones in |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -63,13 +63,25 @@ func GetChart(url string, conf *action.Configuration, repositoryNamespace string | |||||||||||||||||||||
| return loader.Load(chartPath) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| func GetChartFromURL(url string, conf *action.Configuration, namespace string, client dynamic.Interface, coreClient corev1client.CoreV1Interface, filesCleanup bool) (*chart.Chart, error) { | ||||||||||||||||||||||
| // GetChartFromURL loads a chart from an OCI or direct HTTP(S) URL. basicAuthSecretName names a | ||||||||||||||||||||||
| // Secret in namespace with username and password keys when the registry requires authentication. | ||||||||||||||||||||||
| func GetChartFromURL(url string, conf *action.Configuration, namespace string, client dynamic.Interface, coreClient corev1client.CoreV1Interface, filesCleanup bool, basicAuthSecretName string) (*chart.Chart, error) { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if !isValidChartURL(url) { | ||||||||||||||||||||||
| return nil, fmt.Errorf("invalid chart URL: %s, must be oci:// URL or http(s)://*.tgz", url) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| cmd := action.NewInstall(conf) | ||||||||||||||||||||||
| cmd.Namespace = namespace | ||||||||||||||||||||||
| if err := applyBasicAuthFromSecret(cmd, coreClient, namespace, basicAuthSecretName); err != nil { | ||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if basicAuthSecretName != "" { | ||||||||||||||||||||||
| rc, err := RegistryClientWithBasicAuth(false, false, cmd.Username, cmd.Password) | ||||||||||||||||||||||
|
Comment on lines
+75
to
+79
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This structure strikes me as odd: I suspect that we would prefer not to call
Suggested change
(You might need to tweak the declaration/usage of |
||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| return nil, fmt.Errorf("failed to configure OCI registry client: %w", err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| cmd.SetRegistryClient(rc) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||||||||||||||||||
| chartLocation, err := cmd.ChartPathOptions.LocateChart(url, settings) | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| return nil, fmt.Errorf("error getting chart from URL: %v", err) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,14 +12,8 @@ import ( | |
| // newRegistryClient is a package-level variable to allow mocking in tests | ||
| var newRegistryClient = registry.NewClient | ||
|
|
||
| func GetDefaultOCIRegistry(conf *action.Configuration) error { | ||
| return GetOCIRegistry(conf, false, false) | ||
| } | ||
|
|
||
| func GetOCIRegistry(conf *action.Configuration, skipTLSVerify bool, plainHTTP bool) error { | ||
| if conf == nil { | ||
| return fmt.Errorf("action configuration cannot be nil") | ||
| } | ||
| // registryClientOptions returns the same options used by GetOCIRegistry for TLS / plain-HTTP behavior. | ||
| func registryClientOptions(skipTLSVerify, plainHTTP bool) []registry.ClientOption { | ||
|
Comment on lines
+15
to
+16
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This docstring isn't very useful, and, worse, it is describing the behavior of code which is elsewhere (which risks having that code change without this description being updated). Can you rework this in a way which doesn't mention (FWIW, the fact that the guts of this function used to be the guts of |
||
| opts := []registry.ClientOption{ | ||
| registry.ClientOptDebug(false), | ||
| } | ||
|
|
@@ -33,7 +27,28 @@ func GetOCIRegistry(conf *action.Configuration, skipTLSVerify bool, plainHTTP bo | |
| } | ||
| opts = append(opts, registry.ClientOptHTTPClient(&http.Client{Transport: transport})) | ||
| } | ||
| registryClient, err := newRegistryClient(opts...) | ||
| return opts | ||
| } | ||
|
|
||
| // RegistryClientWithBasicAuth builds a registry.Client with the same TLS/plain-HTTP settings as | ||
| // GetDefaultOCIRegistry (skipTLSVerify=false, plainHTTP=false) plus OCI basic auth. | ||
| // Helm's OCI getter uses Configuration.RegistryClient when set and does not apply ChartPathOptions | ||
| // username/password to that client; credentials must be set on the registry client via ClientOptBasicAuth. | ||
| func RegistryClientWithBasicAuth(skipTLSVerify, plainHTTP bool, username, password string) (*registry.Client, error) { | ||
| opts := registryClientOptions(skipTLSVerify, plainHTTP) | ||
| opts = append(opts, registry.ClientOptBasicAuth(username, password)) | ||
| return newRegistryClient(opts...) | ||
| } | ||
|
|
||
| func GetDefaultOCIRegistry(conf *action.Configuration) error { | ||
| return GetOCIRegistry(conf, false, false) | ||
| } | ||
|
|
||
| func GetOCIRegistry(conf *action.Configuration, skipTLSVerify bool, plainHTTP bool) error { | ||
| if conf == nil { | ||
| return fmt.Errorf("action configuration cannot be nil") | ||
| } | ||
| registryClient, err := newRegistryClient(registryClientOptions(skipTLSVerify, plainHTTP)...) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to create registry client: %w", err) | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The label at
HelmURLInstallForm.tsxline 185 doesn't have a trailing period...should we remove the one here?