@@ -17,9 +17,9 @@ limitations under the License.
17
17
package chart
18
18
19
19
import (
20
+ "bytes"
20
21
"context"
21
22
"fmt"
22
- "io"
23
23
"os"
24
24
"path/filepath"
25
25
@@ -33,6 +33,7 @@ import (
33
33
34
34
"github.com/fluxcd/source-controller/internal/fs"
35
35
"github.com/fluxcd/source-controller/internal/helm/repository"
36
+ "github.com/fluxcd/source-controller/internal/util"
36
37
)
37
38
38
39
type remoteChartBuilder struct {
@@ -61,7 +62,7 @@ func NewRemoteBuilder(repository *repository.ChartRepository) Builder {
61
62
// After downloading the chart, it is only packaged if required due to BuildOptions
62
63
// modifying the chart, otherwise the exact data as retrieved from the repository
63
64
// 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 ) {
65
66
remoteRef , ok := ref .(RemoteReference )
66
67
if ! ok {
67
68
err := fmt .Errorf ("expected remote chart reference" )
@@ -105,6 +106,19 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
105
106
106
107
requiresPackaging := len (opts .GetValuesFiles ()) != 0 || opts .VersionMetadata != ""
107
108
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
+
108
122
// If all the following is true, we do not need to download and/or build the chart:
109
123
// - Chart name from cached chart matches resolved name
110
124
// - Chart version from cached chart matches calculated version
@@ -115,6 +129,14 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
115
129
// and continue the build
116
130
if err = curMeta .Validate (); err == nil {
117
131
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
+ }
118
140
result .Path = opts .CachedChart
119
141
result .ValuesFiles = opts .GetValuesFiles ()
120
142
result .Packaged = requiresPackaging
@@ -126,15 +148,39 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
126
148
127
149
// Download the package for the resolved version
128
150
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
+
129
154
if err != nil {
130
155
err = fmt .Errorf ("failed to download chart for remote reference: %w" , err )
131
156
return result , & BuildError {Reason : ErrChartPull , Err : err }
132
157
}
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
+ }
133
179
134
180
// Use literal chart copy from remote if no custom values files options are
135
181
// set or version metadata isn't set.
136
182
if ! requiresPackaging {
137
- if err = validatePackageAndWriteToPath (res , p ); err != nil {
183
+ if err = validatePackageAndWriteToPath (chartBuf , p ); err != nil {
138
184
return nil , & BuildError {Reason : ErrChartPull , Err : err }
139
185
}
140
186
result .Path = p
@@ -143,7 +189,7 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
143
189
144
190
// Load the chart and merge chart values
145
191
var chart * helmchart.Chart
146
- if chart , err = loader .LoadArchive (res ); err != nil {
192
+ if chart , err = loader .LoadArchive (bytes . NewBuffer ( chartBuf ) ); err != nil {
147
193
err = fmt .Errorf ("failed to load downloaded chart: %w" , err )
148
194
return result , & BuildError {Reason : ErrChartPackage , Err : err }
149
195
}
@@ -166,6 +212,7 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
166
212
if err = packageToPath (chart , p ); err != nil {
167
213
return nil , & BuildError {Reason : ErrChartPackage , Err : err }
168
214
}
215
+
169
216
result .Path = p
170
217
result .Packaged = true
171
218
return result , nil
@@ -202,18 +249,12 @@ func mergeChartValues(chart *helmchart.Chart, paths []string) (map[string]interf
202
249
203
250
// validatePackageAndWriteToPath atomically writes the packaged chart from reader
204
251
// 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 )
210
254
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 )
217
258
}
218
259
meta , err := LoadChartMetadataFromArchive (tmpFile .Name ())
219
260
if err != nil {
0 commit comments