From b37c1c622b4e28a066eb24fada5a00c27d097d7c Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Fri, 11 Apr 2025 12:08:42 +0200 Subject: [PATCH] Make it possible to configure via env var max chart and file size To avoid blocking user with the security fix introduced in e4da497, I think we should permit to increase max size to greater values via env var. I don't know if we should enforce a limit. It's probably unnecessary. We probably need to document this capability too. Signed-off-by: Benoit Tigeot --- pkg/action/install.go | 4 + pkg/action/pull.go | 14 ++- pkg/action/upgrade.go | 4 + pkg/chart/v2/loader/archive.go | 85 ++++++++++++++----- pkg/chart/v2/loader/directory.go | 38 +++++++-- pkg/chart/v2/loader/load.go | 35 +++++++- pkg/chart/v2/util/expand.go | 20 ++++- pkg/cli/environment.go | 22 +++++ pkg/cmd/install.go | 11 ++- pkg/cmd/install_test.go | 6 ++ pkg/cmd/pull.go | 2 + pkg/cmd/pull_test.go | 6 ++ pkg/cmd/root.go | 2 + pkg/cmd/testdata/output/env-comp.txt | 2 + .../install-with-restricted-chart-size.txt | 1 + .../output/upgrade-failed-max-chart-size.txt | 1 + pkg/cmd/upgrade.go | 13 ++- pkg/cmd/upgrade_test.go | 7 ++ 18 files changed, 237 insertions(+), 36 deletions(-) create mode 100644 pkg/cmd/testdata/output/install-with-restricted-chart-size.txt create mode 100644 pkg/cmd/testdata/output/upgrade-failed-max-chart-size.txt diff --git a/pkg/action/install.go b/pkg/action/install.go index 5ae12904d..735d80eba 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -131,6 +131,10 @@ type Install struct { // Lock to control raceconditions when the process receives a SIGTERM Lock sync.Mutex goroutineCount atomic.Int32 + // MaxChartSize is the maximum size of a decompressed chart in bytes + MaxChartSize int64 + // MaxChartFileSize is the maximum size of a single file in a chart in bytes + MaxChartFileSize int64 } // ChartPathOptions captures common options used for controlling chart paths diff --git a/pkg/action/pull.go b/pkg/action/pull.go index be71d0ed0..f41951d42 100644 --- a/pkg/action/pull.go +++ b/pkg/action/pull.go @@ -22,6 +22,7 @@ import ( "path/filepath" "strings" + "helm.sh/helm/v4/pkg/chart/v2/loader" chartutil "helm.sh/helm/v4/pkg/chart/v2/util" "helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/pkg/downloader" @@ -44,6 +45,10 @@ type Pull struct { UntarDir string DestDir string cfg *Configuration + // MaxChartSize is the maximum decompressed size of a chart in bytes + MaxChartSize int64 + // MaxChartFileSize is the maximum size of a single file in a chart in bytes + MaxChartFileSize int64 } type PullOpt func(*Pull) @@ -169,7 +174,14 @@ func (p *Pull) Run(chartRef string) (string, error) { return out.String(), fmt.Errorf("failed to untar: a file or directory with the name %s already exists", udCheck) } - return out.String(), chartutil.ExpandFile(ud, saved) + opts := loader.DefaultChartLoadOptions + if p.MaxChartSize > 0 { + opts.MaxDecompressedChartSize = p.MaxChartSize + } + if p.MaxChartFileSize > 0 { + opts.MaxDecompressedFileSize = p.MaxChartFileSize + } + return out.String(), chartutil.ExpandFileWithOptions(ud, saved, opts) } return out.String(), nil } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 3688adf0e..142c82b59 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -127,6 +127,10 @@ type Upgrade struct { EnableDNS bool // TakeOwnership will skip the check for helm annotations and adopt all existing resources. TakeOwnership bool + // MaxChartSize is the maximum decompressed size of a chart in bytes + MaxChartSize int64 + // MaxChartFileSize is the maximum size of a single file in a chart in bytes + MaxChartFileSize int64 } type resultMessage struct { diff --git a/pkg/chart/v2/loader/archive.go b/pkg/chart/v2/loader/archive.go index b9f370f56..ae8bd7d12 100644 --- a/pkg/chart/v2/loader/archive.go +++ b/pkg/chart/v2/loader/archive.go @@ -32,27 +32,56 @@ import ( chart "helm.sh/helm/v4/pkg/chart/v2" ) -// MaxDecompressedChartSize is the maximum size of a chart archive that will be -// decompressed. This is the decompressed size of all the files. -// The default value is 100 MiB. -var MaxDecompressedChartSize int64 = 100 * 1024 * 1024 // Default 100 MiB +var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`) -// MaxDecompressedFileSize is the size of the largest file that Helm will attempt to load. -// The size of the file is the decompressed version of it when it is stored in an archive. -var MaxDecompressedFileSize int64 = 5 * 1024 * 1024 // Default 5 MiB +type ChartLoadOptions struct { + MaxDecompressedChartSize int64 + MaxDecompressedFileSize int64 +} -var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`) +// DefaultChartLoadOptions provides the default size limits +var DefaultChartLoadOptions = ChartLoadOptions{ + // MaxDecompressedChartSize is the maximum size of a chart archive that will be + // decompressed. This is the decompressed size of all the files. + MaxDecompressedChartSize: 100 * 1024 * 1024, // 100 MiB + // MaxDecompressedFileSize is the size of the largest file that Helm will attempt to load. + // The size of the file is the decompressed version of it when it is stored in an archive. + MaxDecompressedFileSize: 5 * 1024 * 1024, // 5 MiB +} + +// FileLoader with embedded options +type FileLoader struct { + path string + opts ChartLoadOptions +} -// FileLoader loads a chart from a file -type FileLoader string +// NewFileLoader creates a file loader with custom options +func NewFileLoader(path string, opts ChartLoadOptions) FileLoader { + return FileLoader{path: path, opts: opts} +} -// Load loads a chart +// NewDefaultFileLoader creates a file loader with default options +func NewDefaultFileLoader(path string) FileLoader { + return FileLoader{path: path, opts: DefaultChartLoadOptions} +} + +// Load loads a chart using default options func (l FileLoader) Load() (*chart.Chart, error) { - return LoadFile(string(l)) + return LoadFileWithOptions(l.path, DefaultChartLoadOptions) } -// LoadFile loads from an archive file. +// LoadWithOptions loads a chart using the provided options +func (l FileLoader) LoadWithOptions() (*chart.Chart, error) { + return LoadFileWithOptions(l.path, l.opts) +} + +// LoadFile load a chart with default options func LoadFile(name string) (*chart.Chart, error) { + return LoadFileWithOptions(name, DefaultChartLoadOptions) +} + +// LoadFile loads from an archive file with the provided options +func LoadFileWithOptions(name string, opts ChartLoadOptions) (*chart.Chart, error) { if fi, err := os.Stat(name); err != nil { return nil, err } else if fi.IsDir() { @@ -70,7 +99,7 @@ func LoadFile(name string) (*chart.Chart, error) { return nil, err } - c, err := LoadArchive(raw) + c, err := LoadArchiveWithOptions(raw, opts) if err != nil { if err == gzip.ErrHeader { return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %s)", name, err) @@ -117,8 +146,15 @@ func isGZipApplication(data []byte) bool { // LoadArchiveFiles reads in files out of an archive into memory. This function // performs important path security checks and should always be used before -// expanding a tarball +// expanding a tarball. It use default options. func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { + return LoadArchiveFilesWithOptions(in, DefaultChartLoadOptions) +} + +// LoadArchiveFiles reads in files out of an archive into memory. This function +// performs important path security checks and should always be used before +// expanding a tarball. It uses the provided options. +func LoadArchiveFilesWithOptions(in io.Reader, opts ChartLoadOptions) ([]*BufferedFile, error) { unzipped, err := gzip.NewReader(in) if err != nil { return nil, err @@ -127,7 +163,7 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { files := []*BufferedFile{} tr := tar.NewReader(unzipped) - remainingSize := MaxDecompressedChartSize + remainingSize := opts.MaxDecompressedChartSize for { b := bytes.NewBuffer(nil) hd, err := tr.Next() @@ -188,11 +224,11 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { } if hd.Size > remainingSize { - return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d", MaxDecompressedChartSize) + return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d bytes", opts.MaxDecompressedChartSize) } - if hd.Size > MaxDecompressedFileSize { - return nil, fmt.Errorf("decompressed chart file %q is larger than the maximum file size %d", hd.Name, MaxDecompressedFileSize) + if hd.Size > opts.MaxDecompressedFileSize { + return nil, fmt.Errorf("decompressed chart file %q is larger than the maximum file size %d bytes", hd.Name, opts.MaxDecompressedFileSize) } limitedReader := io.LimitReader(tr, remainingSize) @@ -208,7 +244,7 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { // is the one that goes over the limit. It assumes the Size stored in the tar header // is correct, something many applications do. if bytesWritten < hd.Size || remainingSize <= 0 { - return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d", MaxDecompressedChartSize) + return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d bytes", opts.MaxDecompressedChartSize) } data := bytes.TrimPrefix(b.Bytes(), utf8bom) @@ -223,9 +259,14 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { return files, nil } -// LoadArchive loads from a reader containing a compressed tar archive. +// LoadArchive loads from a reader containing a compressed tar archive with default options func LoadArchive(in io.Reader) (*chart.Chart, error) { - files, err := LoadArchiveFiles(in) + return LoadArchiveWithOptions(in, DefaultChartLoadOptions) +} + +// LoadArchive loads from a reader containing a compressed tar archive with the provided options +func LoadArchiveWithOptions(in io.Reader, opts ChartLoadOptions) (*chart.Chart, error) { + files, err := LoadArchiveFilesWithOptions(in, opts) if err != nil { return nil, err } diff --git a/pkg/chart/v2/loader/directory.go b/pkg/chart/v2/loader/directory.go index 4f72925dc..7a2d38ecf 100644 --- a/pkg/chart/v2/loader/directory.go +++ b/pkg/chart/v2/loader/directory.go @@ -31,17 +31,45 @@ import ( var utf8bom = []byte{0xEF, 0xBB, 0xBF} // DirLoader loads a chart from a directory -type DirLoader string +type DirLoader struct { + path string + opts ChartLoadOptions +} + +// NewDirLoader creates a new directory loader with default options +func NewDefaultDirLoader(path string) DirLoader { + return DirLoader{path: path, opts: DefaultChartLoadOptions} +} + +// NewDirLoader creates a new directory loader with custom options +func NewDirLoader(path string, opts ChartLoadOptions) DirLoader { + return DirLoader{path: path, opts: opts} +} // Load loads the chart func (l DirLoader) Load() (*chart.Chart, error) { - return LoadDir(string(l)) + return LoadDir(l.path) +} + +// LoadWithOptions loads the chart with custom options +func (l DirLoader) LoadWithOptions() (*chart.Chart, error) { + return LoadDirWithOptions(l.path, l.opts) +} + +// LoadDirWithOptions loads from a directory with default options +func LoadDir(dir string) (*chart.Chart, error) { + return LoadDirWithOptions(dir, DefaultChartLoadOptions) +} + +// LoadDirWithOptions loads from a directory with custom options +func (l DirLoader) LoadDirWithOptions() (*chart.Chart, error) { + return LoadDirWithOptions(l.path, l.opts) } // LoadDir loads from a directory. // // This loads charts only from directories. -func LoadDir(dir string) (*chart.Chart, error) { +func LoadDirWithOptions(dir string, opts ChartLoadOptions) (*chart.Chart, error) { topdir, err := filepath.Abs(dir) if err != nil { return nil, err @@ -99,8 +127,8 @@ func LoadDir(dir string) (*chart.Chart, error) { return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", name) } - if fi.Size() > MaxDecompressedFileSize { - return fmt.Errorf("chart file %q is larger than the maximum file size %d", fi.Name(), MaxDecompressedFileSize) + if fi.Size() > opts.MaxDecompressedFileSize { + return fmt.Errorf("chart file %q is larger than the maximum file size %d", fi.Name(), opts.MaxDecompressedFileSize) } data, err := os.ReadFile(name) diff --git a/pkg/chart/v2/loader/load.go b/pkg/chart/v2/loader/load.go index 0c025e183..6bec8af57 100644 --- a/pkg/chart/v2/loader/load.go +++ b/pkg/chart/v2/loader/load.go @@ -38,6 +38,7 @@ import ( // ChartLoader loads a chart. type ChartLoader interface { Load() (*chart.Chart, error) + LoadWithOptions() (*chart.Chart, error) } // Loader returns a new ChartLoader appropriate for the given chart name @@ -47,9 +48,23 @@ func Loader(name string) (ChartLoader, error) { return nil, err } if fi.IsDir() { - return DirLoader(name), nil + return NewDefaultDirLoader(name), nil } - return FileLoader(name), nil + return NewDefaultFileLoader(name), nil + +} + +// WithOptions returns a new ChartLoader appropriate for the given chart name +// with the provided options. +func WithOptions(name string, opts ChartLoadOptions) (ChartLoader, error) { + fi, err := os.Stat(name) + if err != nil { + return nil, err + } + if fi.IsDir() { + return NewDirLoader(name, opts), nil + } + return NewFileLoader(name, opts), nil } // Load takes a string name, tries to resolve it to a file or directory, and then loads it. @@ -67,6 +82,22 @@ func Load(name string) (*chart.Chart, error) { return l.Load() } +// LoadWithOptions takes a string name, tries to resolve it to a file or directory, +// and then loads it. It uses the provided options to load the chart. +// +// This is the preferred way to load a chart. It will discover the chart encoding +// and hand off to the appropriate chart reader. +// +// If a .helmignore file is present, the directory loader will skip loading any files +// matching it. But .helmignore is not evaluated when reading out of an archive. +func LoadWithOptions(name string, opts ChartLoadOptions) (*chart.Chart, error) { + l, err := WithOptions(name, opts) + if err != nil { + return nil, err + } + return l.LoadWithOptions() +} + // BufferedFile represents an archive file buffered for later processing. type BufferedFile struct { Name string diff --git a/pkg/chart/v2/util/expand.go b/pkg/chart/v2/util/expand.go index 9d08571ed..393212947 100644 --- a/pkg/chart/v2/util/expand.go +++ b/pkg/chart/v2/util/expand.go @@ -30,9 +30,16 @@ import ( "helm.sh/helm/v4/pkg/chart/v2/loader" ) -// Expand uncompresses and extracts a chart into the specified directory. +// Expand uncompresses and extracts a chart into the specified directory +// with default options. func Expand(dir string, r io.Reader) error { - files, err := loader.LoadArchiveFiles(r) + return ExpandWithOptions(dir, r, loader.DefaultChartLoadOptions) +} + +// Expand uncompresses and extracts a chart into the specified directory +// with custom options. +func ExpandWithOptions(dir string, r io.Reader, opts loader.ChartLoadOptions) error { + files, err := loader.LoadArchiveFilesWithOptions(r, opts) if err != nil { return err } @@ -84,11 +91,18 @@ func Expand(dir string, r io.Reader) error { } // ExpandFile expands the src file into the dest directory. +// It uses default options to control the loading of the chart. func ExpandFile(dest, src string) error { + return ExpandFileWithOptions(dest, src, loader.DefaultChartLoadOptions) +} + +// ExpandFile expands the src file into the dest directory. +// It uses custom options to control the loading of the chart. +func ExpandFileWithOptions(dest, src string, opts loader.ChartLoadOptions) error { h, err := os.Open(src) if err != nil { return err } defer h.Close() - return Expand(dest, h) + return ExpandWithOptions(dest, h, opts) } diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 106d24336..31e3c50f5 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -93,6 +93,10 @@ type EnvSettings struct { ColorMode string // ContentCache is the location where cached charts are stored ContentCache string + // MaxChartSize is the maximum size of a decompressed chart in bytes + MaxChartSize int64 + // MaxChartFileSize is the maximum size of a single file in a chart in bytes + MaxChartFileSize int64 } func New() *EnvSettings { @@ -115,6 +119,8 @@ func New() *EnvSettings { BurstLimit: envIntOr("HELM_BURST_LIMIT", defaultBurstLimit), QPS: envFloat32Or("HELM_QPS", defaultQPS), ColorMode: envColorMode(), + MaxChartSize: envInt64Or("HELM_MAX_CHART_SIZE", 100*1024*1024), // 100 MiB + MaxChartFileSize: envInt64Or("HELM_MAX_FILE_SIZE", 5*1024*1024), // 5 MiB } env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG")) @@ -214,6 +220,20 @@ func envFloat32Or(name string, def float32) float32 { return float32(ret) } +// We want to handle int64 like returned by https://pkg.go.dev/io/fs#FileInfo +func envInt64Or(name string, def int64) int64 { + if name == "" { + return def + } + envVal := envOr(name, strconv.FormatInt(def, 10)) + ret, err := strconv.ParseInt(envVal, 10, 64) + if err != nil { + fmt.Fprintf(os.Stderr, "Warning: Environment variable %s has invalid value %q (expected an integer): %v\n", name, envVal, err) + return def + } + return ret +} + func envCSV(name string) (ls []string) { trimmed := strings.Trim(os.Getenv(name), ", ") if trimmed != "" { @@ -255,6 +275,8 @@ func (s *EnvSettings) EnvVars() map[string]string { "HELM_MAX_HISTORY": strconv.Itoa(s.MaxHistory), "HELM_BURST_LIMIT": strconv.Itoa(s.BurstLimit), "HELM_QPS": strconv.FormatFloat(float64(s.QPS), 'f', 2, 32), + "HELM_MAX_CHART_SIZE": strconv.FormatInt(s.MaxChartSize, 10), + "HELM_MAX_FILE_SIZE": strconv.FormatInt(s.MaxChartFileSize, 10), // broken, these are populated from helm flags and not kubeconfig. "HELM_KUBECONTEXT": s.KubeContext, diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index c4e121c1f..b23ea64ea 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -178,6 +178,8 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { // it is added separately f := cmd.Flags() f.BoolVar(&client.HideSecret, "hide-secret", false, "hide Kubernetes Secrets when also using the --dry-run flag") + f.Int64Var(&client.MaxChartSize, "max-chart-size", settings.MaxChartSize, "maximum size in bytes for a decompressed chart (default is 100mb)") + f.Int64Var(&client.MaxChartFileSize, "max-file-size", settings.MaxChartFileSize, "maximum size in bytes for a single file in a chart (default is 5mb)") bindOutputFlag(cmd, &outfmt) bindPostRenderFlag(cmd, &client.PostRenderer, settings) @@ -264,8 +266,15 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options return nil, err } + opts := loader.DefaultChartLoadOptions + if client.MaxChartSize > 0 { + opts.MaxDecompressedChartSize = client.MaxChartSize + } + if client.MaxChartFileSize > 0 { + opts.MaxDecompressedFileSize = client.MaxChartFileSize + } // Check chart dependencies to make sure all are present in /charts - chartRequested, err := loader.Load(cp) + chartRequested, err := loader.LoadWithOptions(cp, opts) if err != nil { return nil, err } diff --git a/pkg/cmd/install_test.go b/pkg/cmd/install_test.go index f0f12e4f7..2ae791ba7 100644 --- a/pkg/cmd/install_test.go +++ b/pkg/cmd/install_test.go @@ -274,6 +274,12 @@ func TestInstall(t *testing.T) { wantError: true, golden: "output/install-hide-secret.txt", }, + { + name: "install with restricted max size", + cmd: "install too-big testdata/testcharts/compressedchart-0.1.0.tgz --max-chart-size=42", + wantError: true, + golden: "output/install-with-restricted-chart-size.txt", + }, } runTestCmd(t, tests) diff --git a/pkg/cmd/pull.go b/pkg/cmd/pull.go index e3d93c049..5441ac223 100644 --- a/pkg/cmd/pull.go +++ b/pkg/cmd/pull.go @@ -89,6 +89,8 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.BoolVar(&client.VerifyLater, "prov", false, "fetch the provenance file, but don't perform verification") f.StringVar(&client.UntarDir, "untardir", ".", "if untar is specified, this flag specifies the name of the directory into which the chart is expanded") f.StringVarP(&client.DestDir, "destination", "d", ".", "location to write the chart. If this and untardir are specified, untardir is appended to this") + f.Int64Var(&client.MaxChartSize, "max-chart-size", settings.MaxChartSize, "maximum size in bytes for a decompressed chart (default is 100mb)") + f.Int64Var(&client.MaxChartFileSize, "max-file-size", settings.MaxChartFileSize, "maximum size in bytes for a single file in a chart (default is 5mb)") addChartPathOptionsFlags(f, &client.ChartPathOptions) err := cmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { diff --git a/pkg/cmd/pull_test.go b/pkg/cmd/pull_test.go index c24bf33b7..37d7a6e50 100644 --- a/pkg/cmd/pull_test.go +++ b/pkg/cmd/pull_test.go @@ -210,6 +210,12 @@ func TestPullCmd(t *testing.T) { wantErrorMsg: "Error: chart reference and version mismatch: 0.2.0 is not 0.1.0", wantError: true, }, + { + name: "Fail because of small max chart size", + args: "test/test1 --max-chart-size=90", + wantError: true, + wantErrorMsg: "decompressed chart is larger than the maximum size 90 bytes", + }, } contentCache := t.TempDir() diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 4753e51fe..c2ec070bd 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -83,6 +83,8 @@ Environment variables: | $HELM_QPS | set the Queries Per Second in cases where a high number of calls exceed the option for higher burst values | | $HELM_COLOR | set color output mode. Allowed values: never, always, auto (default: never) | | $NO_COLOR | set to any non-empty value to disable all colored output (overrides $HELM_COLOR) | +| $HELM_MAX_CHART_SIZE | set the maximum size in bytes for a decompressed chart (default: 100MiB, 0 means use default limit) | +| $HELM_MAX_FILE_SIZE | set the maximum size in bytes for a single file in a chart (default: 5MiB, 0 means use default limit) | Helm stores cache, configuration, and data based on the following configuration order: diff --git a/pkg/cmd/testdata/output/env-comp.txt b/pkg/cmd/testdata/output/env-comp.txt index 9d38ee464..6f560ef58 100644 --- a/pkg/cmd/testdata/output/env-comp.txt +++ b/pkg/cmd/testdata/output/env-comp.txt @@ -13,6 +13,8 @@ HELM_KUBECONTEXT HELM_KUBEINSECURE_SKIP_TLS_VERIFY HELM_KUBETLS_SERVER_NAME HELM_KUBETOKEN +HELM_MAX_CHART_SIZE +HELM_MAX_FILE_SIZE HELM_MAX_HISTORY HELM_NAMESPACE HELM_PLUGINS diff --git a/pkg/cmd/testdata/output/install-with-restricted-chart-size.txt b/pkg/cmd/testdata/output/install-with-restricted-chart-size.txt new file mode 100644 index 000000000..80b6fb512 --- /dev/null +++ b/pkg/cmd/testdata/output/install-with-restricted-chart-size.txt @@ -0,0 +1 @@ +Error: INSTALLATION FAILED: decompressed chart is larger than the maximum size 42 bytes diff --git a/pkg/cmd/testdata/output/upgrade-failed-max-chart-size.txt b/pkg/cmd/testdata/output/upgrade-failed-max-chart-size.txt new file mode 100644 index 000000000..ef713ced2 --- /dev/null +++ b/pkg/cmd/testdata/output/upgrade-failed-max-chart-size.txt @@ -0,0 +1 @@ +Error: decompressed chart is larger than the maximum size 52 bytes diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index c8fbf8bd3..8cf627036 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -193,8 +193,15 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return err } + opts := loader.DefaultChartLoadOptions + if client.MaxChartSize > 0 { + opts.MaxDecompressedChartSize = client.MaxChartSize + } + if client.MaxChartFileSize > 0 { + opts.MaxDecompressedFileSize = client.MaxChartFileSize + } // Check chart dependencies to make sure all are present in /charts - ch, err := loader.Load(chartPath) + ch, err := loader.LoadWithOptions(chartPath, opts) if err != nil { return err } @@ -217,7 +224,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return err } // Reload the chart with the updated Chart.lock file. - if ch, err = loader.Load(chartPath); err != nil { + if ch, err = loader.LoadWithOptions(chartPath, opts); err != nil { return fmt.Errorf("failed reloading chart after repo update: %w", err) } } else { @@ -297,6 +304,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart") f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates") f.BoolVar(&client.TakeOwnership, "take-ownership", false, "if set, upgrade will ignore the check for helm annotations and take ownership of the existing resources") + f.Int64Var(&client.MaxChartSize, "max-chart-size", settings.MaxChartSize, "maximum size in bytes for a decompressed chart (default is 100mb)") + f.Int64Var(&client.MaxChartFileSize, "max-file-size", settings.MaxChartFileSize, "maximum size in bytes for a single file in a chart (default is 5mb)") addChartPathOptionsFlags(f, &client.ChartPathOptions) addValueOptionsFlags(f, valueOpts) bindOutputFlag(cmd, &outfmt) diff --git a/pkg/cmd/upgrade_test.go b/pkg/cmd/upgrade_test.go index 9b17f187d..f41718db6 100644 --- a/pkg/cmd/upgrade_test.go +++ b/pkg/cmd/upgrade_test.go @@ -81,6 +81,7 @@ func TestUpgradeCmd(t *testing.T) { missingDepsPath := "testdata/testcharts/chart-missing-deps" badDepsPath := "testdata/testcharts/chart-bad-requirements" presentDepsPath := "testdata/testcharts/chart-with-subchart-update" + compressedPath := "testdata/testcharts/compressedchart-0.1.0.tgz" relWithStatusMock := func(n string, v int, ch *chart.Chart, status release.Status) *release.Release { return release.Mock(&release.MockReleaseOptions{Name: n, Version: v, Chart: ch, Status: status}) @@ -188,6 +189,12 @@ func TestUpgradeCmd(t *testing.T) { golden: "output/upgrade-uninstalled-with-keep-history.txt", rels: []*release.Release{relWithStatusMock("funny-bunny", 2, ch, release.StatusUninstalled)}, }, + { + name: "upgrade with restricted max size", + cmd: fmt.Sprintf("upgrade too-big '%s' --max-chart-size=52", compressedPath), + wantError: true, + golden: "output/upgrade-failed-max-chart-size.txt", + }, } runTestCmd(t, tests) }