@@ -17,9 +17,9 @@ limitations under the License.
1717package chart
1818
1919import (
20+ "bytes"
2021 "context"
2122 "fmt"
22- "io"
2323 "os"
2424 "path/filepath"
2525
@@ -33,6 +33,7 @@ import (
3333
3434 "github.com/fluxcd/source-controller/internal/fs"
3535 "github.com/fluxcd/source-controller/internal/helm/repository"
36+ "github.com/fluxcd/source-controller/internal/util"
3637)
3738
3839type remoteChartBuilder struct {
@@ -61,7 +62,7 @@ func NewRemoteBuilder(repository *repository.ChartRepository) Builder {
6162// After downloading the chart, it is only packaged if required due to BuildOptions
6263// modifying the chart, otherwise the exact data as retrieved from the repository
6364// is written to p, after validating it to be a chart.
64- func (b * remoteChartBuilder ) Build (_ context.Context , ref Reference , p string , opts BuildOptions ) (* Build , error ) {
65+ func (b * remoteChartBuilder ) Build (_ context.Context , ref Reference , p string , opts BuildOptions , keyring [] byte ) (* Build , error ) {
6566 remoteRef , ok := ref .(RemoteReference )
6667 if ! ok {
6768 err := fmt .Errorf ("expected remote chart reference" )
@@ -105,6 +106,19 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
105106
106107 requiresPackaging := len (opts .GetValuesFiles ()) != 0 || opts .VersionMetadata != ""
107108
109+ verifyProvFile := func (chart , provFile string ) error {
110+ if keyring != nil {
111+ err := VerifyProvenanceFile (bytes .NewReader (keyring ), chart , provFile )
112+ if err != nil {
113+ err = fmt .Errorf ("failed to verify helm chart using provenance file %s: %w" , provFile , err )
114+ return & BuildError {Reason : ErrProvenanceVerification , Err : err }
115+ }
116+ }
117+ return nil
118+ }
119+
120+ var provFilePath string
121+
108122 // If all the following is true, we do not need to download and/or build the chart:
109123 // - Chart name from cached chart matches resolved name
110124 // - Chart version from cached chart matches calculated version
@@ -115,6 +129,14 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
115129 // and continue the build
116130 if err = curMeta .Validate (); err == nil {
117131 if result .Name == curMeta .Name && result .Version == curMeta .Version {
132+ // We can only verify with provenance file if we didn't package the chart ourselves.
133+ if ! requiresPackaging {
134+ provFilePath = provenanceFilePath (opts .CachedChart )
135+ if err = verifyProvFile (opts .CachedChart , provFilePath ); err != nil {
136+ return nil , err
137+ }
138+ result .ProvFilePath = provFilePath
139+ }
118140 result .Path = opts .CachedChart
119141 result .ValuesFiles = opts .GetValuesFiles ()
120142 result .Packaged = requiresPackaging
@@ -126,15 +148,39 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
126148
127149 // Download the package for the resolved version
128150 res , err := b .remote .DownloadChart (cv )
151+ // Deal with the underlying byte slice to avoid having to read the buffer multiple times.
152+ chartBuf := res .Bytes ()
153+
129154 if err != nil {
130155 err = fmt .Errorf ("failed to download chart for remote reference: %w" , err )
131156 return result , & BuildError {Reason : ErrChartPull , Err : err }
132157 }
158+ if keyring != nil {
159+ provFilePath = provenanceFilePath (p )
160+ err := b .remote .DownloadProvenanceFile (cv , provFilePath )
161+ if err != nil {
162+ err = fmt .Errorf ("failed to download provenance file for remote reference: %w" , err )
163+ return nil , & BuildError {Reason : ErrChartPull , Err : err }
164+ }
165+ // Write the remote chart temporarily to verify it with provenance file.
166+ // This is needed, since the verification will work only if the .tgz file is untampered.
167+ // But we write the packaged chart to disk under a different name, so the provenance file
168+ // will not be valid for this _new_ packaged chart.
169+ chart , err := util .WriteBytesToFile (chartBuf , fmt .Sprintf ("%s-%s.tgz" , result .Name , result .Version ), false )
170+ defer os .Remove (chart .Name ())
171+ if err != nil {
172+ return nil , err
173+ }
174+ if err = verifyProvFile (chart .Name (), provFilePath ); err != nil {
175+ return nil , err
176+ }
177+ result .ProvFilePath = provFilePath
178+ }
133179
134180 // Use literal chart copy from remote if no custom values files options are
135181 // set or version metadata isn't set.
136182 if ! requiresPackaging {
137- if err = validatePackageAndWriteToPath (res , p ); err != nil {
183+ if err = validatePackageAndWriteToPath (chartBuf , p ); err != nil {
138184 return nil , & BuildError {Reason : ErrChartPull , Err : err }
139185 }
140186 result .Path = p
@@ -143,7 +189,7 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
143189
144190 // Load the chart and merge chart values
145191 var chart * helmchart.Chart
146- if chart , err = loader .LoadArchive (res ); err != nil {
192+ if chart , err = loader .LoadArchive (bytes . NewBuffer ( chartBuf ) ); err != nil {
147193 err = fmt .Errorf ("failed to load downloaded chart: %w" , err )
148194 return result , & BuildError {Reason : ErrChartPackage , Err : err }
149195 }
@@ -166,6 +212,7 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
166212 if err = packageToPath (chart , p ); err != nil {
167213 return nil , & BuildError {Reason : ErrChartPackage , Err : err }
168214 }
215+
169216 result .Path = p
170217 result .Packaged = true
171218 return result , nil
@@ -202,18 +249,12 @@ func mergeChartValues(chart *helmchart.Chart, paths []string) (map[string]interf
202249
203250// validatePackageAndWriteToPath atomically writes the packaged chart from reader
204251// to out while validating it by loading the chart metadata from the archive.
205- func validatePackageAndWriteToPath (reader io.Reader , out string ) error {
206- tmpFile , err := os .CreateTemp ("" , filepath .Base (out ))
207- if err != nil {
208- return fmt .Errorf ("failed to create temporary file for chart: %w" , err )
209- }
252+ func validatePackageAndWriteToPath (b []byte , out string ) error {
253+ tmpFile , err := util .WriteBytesToFile (b , out , true )
210254 defer os .Remove (tmpFile .Name ())
211- if _ , err = tmpFile .ReadFrom (reader ); err != nil {
212- _ = tmpFile .Close ()
213- return fmt .Errorf ("failed to write chart to file: %w" , err )
214- }
215- if err = tmpFile .Close (); err != nil {
216- return err
255+
256+ if err != nil {
257+ return fmt .Errorf ("failed to write packaged chart to temp file: %w" , err )
217258 }
218259 meta , err := LoadChartMetadataFromArchive (tmpFile .Name ())
219260 if err != nil {
0 commit comments