From 76b9a5ffd83a0ceb0ee5e7572b4ac5baaf891f4e Mon Sep 17 00:00:00 2001 From: Javier Rodriguez Date: Wed, 24 Apr 2024 10:30:25 +0200 Subject: [PATCH 1/2] feat(materials): Helm Chart material support Signed-off-by: Javier Rodriguez --- README.md | 1 + .../workflowcontract/v1/crafting_schema.ts | 6 + .../workflowcontract/v1/crafting_schema.pb.go | 34 +++-- .../workflowcontract/v1/crafting_schema.proto | 1 + go.mod | 2 +- .../crafter/materials/helmchart.go | 128 ++++++++++++++++ .../crafter/materials/helmchart_test.go | 137 ++++++++++++++++++ .../crafter/materials/materials.go | 2 + .../materials/testdata/missing-chartyaml.tgz | Bin 0 -> 14491 bytes .../materials/testdata/missing-valuesyaml.tgz | Bin 0 -> 14098 bytes .../materials/testdata/valid-chart.tgz | Bin 0 -> 13845 bytes 11 files changed, 295 insertions(+), 16 deletions(-) create mode 100644 internal/attestation/crafter/materials/helmchart.go create mode 100644 internal/attestation/crafter/materials/helmchart_test.go create mode 100644 internal/attestation/crafter/materials/testdata/missing-chartyaml.tgz create mode 100644 internal/attestation/crafter/materials/testdata/missing-valuesyaml.tgz create mode 100644 internal/attestation/crafter/materials/testdata/valid-chart.tgz diff --git a/README.md b/README.md index d8b9f5e35..83051e523 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ Chainloop supports the collection of the following pieces of evidence types: - [OpenVEX](https://github.com/openvex) - [SARIF](https://docs.oasis-open.org/sarif/sarif/v2.1.0/) - [JUnit](https://www.ibm.com/docs/en/developer-for-zos/14.1?topic=formats-junit-xml-format) +- [Helm Charts](https://helm.sh/docs/topics/charts/) - Generic Artifact Types - Key-Value metadata pairs diff --git a/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts b/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts index ba9378296..f508c5abc 100644 --- a/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts +++ b/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts @@ -119,6 +119,7 @@ export enum CraftingSchema_Material_MaterialType { * https://github.com/microsoft/sarif-tutorials/blob/main/docs/1-Introduction.md */ SARIF = 9, + HELM_CHART = 10, UNRECOGNIZED = -1, } @@ -154,6 +155,9 @@ export function craftingSchema_Material_MaterialTypeFromJSON(object: any): Craft case 9: case "SARIF": return CraftingSchema_Material_MaterialType.SARIF; + case 10: + case "HELM_CHART": + return CraftingSchema_Material_MaterialType.HELM_CHART; case -1: case "UNRECOGNIZED": default: @@ -183,6 +187,8 @@ export function craftingSchema_Material_MaterialTypeToJSON(object: CraftingSchem return "CSAF_VEX"; case CraftingSchema_Material_MaterialType.SARIF: return "SARIF"; + case CraftingSchema_Material_MaterialType.HELM_CHART: + return "HELM_CHART"; case CraftingSchema_Material_MaterialType.UNRECOGNIZED: default: return "UNRECOGNIZED"; diff --git a/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go b/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go index 042c12423..d7a705678 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go @@ -113,22 +113,24 @@ const ( CraftingSchema_Material_CSAF_VEX CraftingSchema_Material_MaterialType = 8 // Static analysis output format // https://github.com/microsoft/sarif-tutorials/blob/main/docs/1-Introduction.md - CraftingSchema_Material_SARIF CraftingSchema_Material_MaterialType = 9 + CraftingSchema_Material_SARIF CraftingSchema_Material_MaterialType = 9 + CraftingSchema_Material_HELM_CHART CraftingSchema_Material_MaterialType = 10 ) // Enum value maps for CraftingSchema_Material_MaterialType. var ( CraftingSchema_Material_MaterialType_name = map[int32]string{ - 0: "MATERIAL_TYPE_UNSPECIFIED", - 1: "STRING", - 2: "CONTAINER_IMAGE", - 3: "ARTIFACT", - 4: "SBOM_CYCLONEDX_JSON", - 5: "SBOM_SPDX_JSON", - 6: "JUNIT_XML", - 7: "OPENVEX", - 8: "CSAF_VEX", - 9: "SARIF", + 0: "MATERIAL_TYPE_UNSPECIFIED", + 1: "STRING", + 2: "CONTAINER_IMAGE", + 3: "ARTIFACT", + 4: "SBOM_CYCLONEDX_JSON", + 5: "SBOM_SPDX_JSON", + 6: "JUNIT_XML", + 7: "OPENVEX", + 8: "CSAF_VEX", + 9: "SARIF", + 10: "HELM_CHART", } CraftingSchema_Material_MaterialType_value = map[string]int32{ "MATERIAL_TYPE_UNSPECIFIED": 0, @@ -141,6 +143,7 @@ var ( "OPENVEX": 7, "CSAF_VEX": 8, "SARIF": 9, + "HELM_CHART": 10, } ) @@ -449,7 +452,7 @@ var file_workflowcontract_v1_crafting_schema_proto_rawDesc = []byte{ 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x1a, 0x1b, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x87, 0x08, + 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x97, 0x08, 0x0a, 0x0e, 0x43, 0x72, 0x61, 0x66, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x30, 0x0a, 0x0e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xba, 0x48, 0x06, 0x72, 0x04, 0x0a, @@ -486,7 +489,7 @@ var file_workflowcontract_v1_crafting_schema_proto_rawDesc = []byte{ 0x53, 0x5f, 0x4a, 0x4f, 0x42, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x49, 0x52, 0x43, 0x4c, 0x45, 0x43, 0x49, 0x5f, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x10, 0x05, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x41, 0x47, 0x47, 0x45, 0x52, 0x5f, 0x50, 0x49, 0x50, 0x45, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x06, - 0x1a, 0xc4, 0x03, 0x0a, 0x08, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x5a, 0x0a, + 0x1a, 0xd4, 0x03, 0x0a, 0x08, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x5a, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x61, 0x66, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, @@ -502,7 +505,7 @@ var file_workflowcontract_v1_crafting_schema_proto_rawDesc = []byte{ 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xbe, 0x01, 0x0a, 0x0c, 0x4d, 0x61, 0x74, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xce, 0x01, 0x0a, 0x0c, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x19, 0x4d, 0x41, 0x54, 0x45, 0x52, 0x49, 0x41, 0x4c, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, @@ -514,7 +517,8 @@ var file_workflowcontract_v1_crafting_schema_proto_rawDesc = []byte{ 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x4a, 0x55, 0x4e, 0x49, 0x54, 0x5f, 0x58, 0x4d, 0x4c, 0x10, 0x06, 0x12, 0x0b, 0x0a, 0x07, 0x4f, 0x50, 0x45, 0x4e, 0x56, 0x45, 0x58, 0x10, 0x07, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x53, 0x41, 0x46, 0x5f, 0x56, 0x45, 0x58, 0x10, 0x08, 0x12, 0x09, 0x0a, 0x05, - 0x53, 0x41, 0x52, 0x49, 0x46, 0x10, 0x09, 0x22, 0x46, 0x0a, 0x0a, 0x41, 0x6e, 0x6e, 0x6f, 0x74, + 0x53, 0x41, 0x52, 0x49, 0x46, 0x10, 0x09, 0x12, 0x0e, 0x0a, 0x0a, 0x48, 0x45, 0x4c, 0x4d, 0x5f, + 0x43, 0x48, 0x41, 0x52, 0x54, 0x10, 0x0a, 0x22, 0x46, 0x0a, 0x0a, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0e, 0xba, 0x48, 0x0b, 0x72, 0x09, 0x32, 0x07, 0x5e, 0x5b, 0x5c, 0x77, 0x5d, 0x2b, 0x24, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, diff --git a/app/controlplane/api/workflowcontract/v1/crafting_schema.proto b/app/controlplane/api/workflowcontract/v1/crafting_schema.proto index c66df841c..04aec6628 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema.proto +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema.proto @@ -83,6 +83,7 @@ message CraftingSchema { // Static analysis output format // https://github.com/microsoft/sarif-tutorials/blob/main/docs/1-Introduction.md SARIF = 9; + HELM_CHART = 10; } } } diff --git a/go.mod b/go.mod index f0f533397..1f1a6670b 100644 --- a/go.mod +++ b/go.mod @@ -310,7 +310,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.28.3 // indirect k8s.io/apimachinery v0.28.3 diff --git a/internal/attestation/crafter/materials/helmchart.go b/internal/attestation/crafter/materials/helmchart.go new file mode 100644 index 000000000..ab7ef7136 --- /dev/null +++ b/internal/attestation/crafter/materials/helmchart.go @@ -0,0 +1,128 @@ +// +// Copyright 2024 The Chainloop Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package materials + +import ( + "archive/tar" + "compress/gzip" + "context" + "fmt" + "io" + "os" + "strings" + + schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + api "github.com/chainloop-dev/chainloop/internal/attestation/crafter/api/attestation/v1" + "github.com/chainloop-dev/chainloop/internal/casclient" + "github.com/rs/zerolog" + "gopkg.in/yaml.v2" +) + +const ( + ChartFileName = "Chart.yaml" + ValuesYamlFileName = "values.yaml" +) + +type HelmChartCrafter struct { + backend *casclient.CASBackend + *crafterCommon +} + +func NewHelmChartCrafter(materialSchema *schemaapi.CraftingSchema_Material, backend *casclient.CASBackend, + l *zerolog.Logger) (*HelmChartCrafter, error) { + if materialSchema.Type != schemaapi.CraftingSchema_Material_HELM_CHART { + return nil, fmt.Errorf("material type is not HELM_CHART format") + } + + return &HelmChartCrafter{ + backend: backend, + crafterCommon: &crafterCommon{logger: l, input: materialSchema}, + }, nil +} + +func (c *HelmChartCrafter) Craft(ctx context.Context, filepath string) (*api.Attestation_Material, error) { + // Open the helm chart tar file + f, err := os.Open(filepath) + if err != nil { + return nil, fmt.Errorf("can't open the file: %w", err) + } + defer f.Close() + + // Decompress the file if possible + uncompressedStream, err := gzip.NewReader(f) + if err != nil { + return nil, fmt.Errorf("can't uncompress file, unexpected material type: %w", err) + } + + // Create a tar reader + tarReader := tar.NewReader(uncompressedStream) + + // Flags to track whether required files are found + chartFileValid, chartValuesValid := false, false + + // Iterate through the files in the tar archive + for { + header, err := tarReader.Next() + if err == io.EOF { + // Reached the end of tar archive + break + } + if err != nil { + return nil, fmt.Errorf("error reading tar file: %w", err) + } + + // Check if the file is a regular file + if header.Typeflag != tar.TypeReg { + continue // Skip if it's not a regular file + } + + // Validate Chart.yaml and values.yaml files + if strings.Contains(header.Name, ChartFileName) { + if err := validateYamlFile(tarReader); err != nil { + return nil, fmt.Errorf("invalid Chart.yaml file: %w", err) + } + chartFileValid = true + } else if strings.Contains(header.Name, ValuesYamlFileName) { + if err := validateYamlFile(tarReader); err != nil { + return nil, fmt.Errorf("invalid values.yaml file: %w", err) + } + chartValuesValid = true + } + + // Stop iterating if both files are found + if chartValuesValid && chartFileValid { + break + } + } + + // If the chart.yaml and values.yaml files are not found, return an error + if !chartFileValid || !chartValuesValid { + return nil, fmt.Errorf("missing required files in the helm chart: Chart.yaml and values.yaml") + } + + // Upload and craft the chart + return uploadAndCraft(ctx, c.input, c.backend, filepath, c.logger) +} + +// validateYamlFile validates the YAML file just by trying to unmarshal it +func validateYamlFile(r io.Reader) error { + v := make(map[string]interface{}) + if err := yaml.NewDecoder(r).Decode(v); err != nil { + return fmt.Errorf("failed to unmarshal YAML file: %w", err) + } + + return nil +} diff --git a/internal/attestation/crafter/materials/helmchart_test.go b/internal/attestation/crafter/materials/helmchart_test.go new file mode 100644 index 000000000..3b796e17f --- /dev/null +++ b/internal/attestation/crafter/materials/helmchart_test.go @@ -0,0 +1,137 @@ +// +// Copyright 2024 The Chainloop Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package materials_test + +import ( + "context" + "testing" + + contractAPI "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + attestationApi "github.com/chainloop-dev/chainloop/internal/attestation/crafter/api/attestation/v1" + "github.com/chainloop-dev/chainloop/internal/attestation/crafter/materials" + "github.com/chainloop-dev/chainloop/internal/casclient" + mUploader "github.com/chainloop-dev/chainloop/internal/casclient/mocks" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewHelmChartCrafter(t *testing.T) { + testCases := []struct { + name string + input *contractAPI.CraftingSchema_Material + wantErr bool + }{ + { + name: "happy path", + input: &contractAPI.CraftingSchema_Material{ + Type: contractAPI.CraftingSchema_Material_HELM_CHART, + }, + }, + { + name: "wrong type", + input: &contractAPI.CraftingSchema_Material{ + Type: contractAPI.CraftingSchema_Material_CONTAINER_IMAGE, + }, + wantErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := materials.NewHelmChartCrafter(tc.input, nil, nil) + if tc.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + }) + } +} + +func TestHelmChartCraft(t *testing.T) { + testCases := []struct { + name string + filePath string + wantErr string + wantFilename string + wantDigest string + }{ + { + name: "invalid path", + filePath: "./testdata/non-existing.json", + wantErr: "no such file or directory", + }, + { + name: "missing Chart.yaml file", + filePath: "./testdata/missing-chartyaml.tgz", + wantErr: "missing required files in the helm chart: Chart.yaml and values.yaml", + }, + { + name: "missing values.yaml file", + filePath: "./testdata/missing-valuesyaml.tgz", + wantErr: "missing required files in the helm chart: Chart.yaml and values.yaml", + }, + { + name: "invalid artifact type", + filePath: "./testdata/simple.txt", + wantErr: "unexpected material type", + }, + { + name: "valid artifact type", + filePath: "./testdata/valid-chart.tgz", + wantDigest: "sha256:08a46a850789938ede61d6a53552f48cb8ba74c4e17dcf30c9c50e5783ca6a13", + wantFilename: "valid-chart.tgz", + }, + } + + assert := assert.New(t) + schema := &contractAPI.CraftingSchema_Material{ + Name: "test", + Type: contractAPI.CraftingSchema_Material_HELM_CHART, + } + l := zerolog.Nop() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Mock uploader + uploader := mUploader.NewUploader(t) + if tc.wantErr == "" { + uploader.On("UploadFile", context.TODO(), tc.filePath). + Return(&casclient.UpDownStatus{}, nil) + } + + backend := &casclient.CASBackend{Uploader: uploader} + crafter, err := materials.NewHelmChartCrafter(schema, backend, &l) + require.NoError(t, err) + + got, err := crafter.Craft(context.TODO(), tc.filePath) + if tc.wantErr != "" { + assert.ErrorContains(err, tc.wantErr) + return + } + + require.NoError(t, err) + assert.Equal(contractAPI.CraftingSchema_Material_HELM_CHART.String(), got.MaterialType.String()) + assert.True(got.UploadedToCas) + + // The result includes the digest reference + assert.Equal(&attestationApi.Attestation_Material_Artifact{ + Id: "test", Digest: tc.wantDigest, Name: tc.wantFilename, + }, got.GetArtifact()) + }) + } +} diff --git a/internal/attestation/crafter/materials/materials.go b/internal/attestation/crafter/materials/materials.go index 78c9a8645..c8bea87b2 100644 --- a/internal/attestation/crafter/materials/materials.go +++ b/internal/attestation/crafter/materials/materials.go @@ -169,6 +169,8 @@ func Craft(ctx context.Context, materialSchema *schemaapi.CraftingSchema_Materia crafter, err = NewCSAFVEXCrafter(materialSchema, casBackend, logger) case schemaapi.CraftingSchema_Material_SARIF: crafter, err = NewSARIFCrafter(materialSchema, casBackend, logger) + case schemaapi.CraftingSchema_Material_HELM_CHART: + crafter, err = NewHelmChartCrafter(materialSchema, casBackend, logger) default: return nil, fmt.Errorf("material of type %q not supported yet", materialSchema.Type) } diff --git a/internal/attestation/crafter/materials/testdata/missing-chartyaml.tgz b/internal/attestation/crafter/materials/testdata/missing-chartyaml.tgz new file mode 100644 index 0000000000000000000000000000000000000000..795f6a007190576ab893eb4df1ec117ae5141af0 GIT binary patch literal 14491 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 Q-1F!E1F!fV8~|zo0B1j(Pyhe` literal 0 HcmV?d00001 diff --git a/internal/attestation/crafter/materials/testdata/valid-chart.tgz b/internal/attestation/crafter/materials/testdata/valid-chart.tgz new file mode 100644 index 0000000000000000000000000000000000000000..3fe48899726c3d349328820fc512d217035e4ac4 GIT binary patch literal 13845 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 literal 0 HcmV?d00001 From 520cc8e6241663759e8b5f9163cea31bac27c083 Mon Sep 17 00:00:00 2001 From: Javier Rodriguez Date: Wed, 24 Apr 2024 11:50:45 +0200 Subject: [PATCH 2/2] Tackle feedback Signed-off-by: Javier Rodriguez --- README.md | 1 - .../crafter/materials/helmchart.go | 22 +++++++++++------- .../crafter/materials/helmchart_test.go | 5 ++++ .../materials/testdata/missing-empty.tgz | Bin 0 -> 109 bytes 4 files changed, 18 insertions(+), 10 deletions(-) create mode 100644 internal/attestation/crafter/materials/testdata/missing-empty.tgz diff --git a/README.md b/README.md index 83051e523..d8b9f5e35 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,6 @@ Chainloop supports the collection of the following pieces of evidence types: - [OpenVEX](https://github.com/openvex) - [SARIF](https://docs.oasis-open.org/sarif/sarif/v2.1.0/) - [JUnit](https://www.ibm.com/docs/en/developer-for-zos/14.1?topic=formats-junit-xml-format) -- [Helm Charts](https://helm.sh/docs/topics/charts/) - Generic Artifact Types - Key-Value metadata pairs diff --git a/internal/attestation/crafter/materials/helmchart.go b/internal/attestation/crafter/materials/helmchart.go index ab7ef7136..49e7d1464 100644 --- a/internal/attestation/crafter/materials/helmchart.go +++ b/internal/attestation/crafter/materials/helmchart.go @@ -32,8 +32,10 @@ import ( ) const ( - ChartFileName = "Chart.yaml" - ValuesYamlFileName = "values.yaml" + // chartFileName is the name of the Chart.yaml file in the helm chart + chartFileName = "Chart.yaml" + // chartValuesYamlFileName is the name of the values.yaml file in the helm chart + chartValuesYamlFileName = "values.yaml" ) type HelmChartCrafter struct { @@ -71,7 +73,7 @@ func (c *HelmChartCrafter) Craft(ctx context.Context, filepath string) (*api.Att tarReader := tar.NewReader(uncompressedStream) // Flags to track whether required files are found - chartFileValid, chartValuesValid := false, false + var chartFileValid, chartValuesValid bool // Iterate through the files in the tar archive for { @@ -89,14 +91,16 @@ func (c *HelmChartCrafter) Craft(ctx context.Context, filepath string) (*api.Att continue // Skip if it's not a regular file } - // Validate Chart.yaml and values.yaml files - if strings.Contains(header.Name, ChartFileName) { - if err := validateYamlFile(tarReader); err != nil { + // Validate Chart.yaml and values.yaml files. The files will have prepended the path of the directory + // it was compressed from. So, we can check if the file name contains the required file names + // Ex: helm-chart/Chart.yaml, helm-chart/values.yaml + if strings.Contains(header.Name, chartFileName) { + if err := c.validateYamlFile(tarReader); err != nil { return nil, fmt.Errorf("invalid Chart.yaml file: %w", err) } chartFileValid = true - } else if strings.Contains(header.Name, ValuesYamlFileName) { - if err := validateYamlFile(tarReader); err != nil { + } else if strings.Contains(header.Name, chartValuesYamlFileName) { + if err := c.validateYamlFile(tarReader); err != nil { return nil, fmt.Errorf("invalid values.yaml file: %w", err) } chartValuesValid = true @@ -118,7 +122,7 @@ func (c *HelmChartCrafter) Craft(ctx context.Context, filepath string) (*api.Att } // validateYamlFile validates the YAML file just by trying to unmarshal it -func validateYamlFile(r io.Reader) error { +func (c *HelmChartCrafter) validateYamlFile(r io.Reader) error { v := make(map[string]interface{}) if err := yaml.NewDecoder(r).Decode(v); err != nil { return fmt.Errorf("failed to unmarshal YAML file: %w", err) diff --git a/internal/attestation/crafter/materials/helmchart_test.go b/internal/attestation/crafter/materials/helmchart_test.go index 3b796e17f..224a0ebfa 100644 --- a/internal/attestation/crafter/materials/helmchart_test.go +++ b/internal/attestation/crafter/materials/helmchart_test.go @@ -76,6 +76,11 @@ func TestHelmChartCraft(t *testing.T) { filePath: "./testdata/non-existing.json", wantErr: "no such file or directory", }, + { + name: "empty tarball", + filePath: "./testdata/missing-empty.tgz", + wantErr: "missing required files in the helm chart: Chart.yaml and values.yaml", + }, { name: "missing Chart.yaml file", filePath: "./testdata/missing-chartyaml.tgz", diff --git a/internal/attestation/crafter/materials/testdata/missing-empty.tgz b/internal/attestation/crafter/materials/testdata/missing-empty.tgz new file mode 100644 index 0000000000000000000000000000000000000000..ec8ba757af0b2c9deee8472ac2b8e8f574a394b4 GIT binary patch literal 109 zcmV-z0FwV7iwFQ<(