diff --git a/README.md b/README.md index 1c95a80db..97ecb922a 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,7 @@ Chainloop supports the collection of the following pieces of evidence types: - [SPDX SBOM](https://spdx.dev/specifications/) - [CSAF VEX](https://docs.oasis-open.org/csaf/csaf/v2.0/os/csaf-v2.0-os.html) - [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) - 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 3a0718797..182e67a75 100644 --- a/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts +++ b/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts @@ -96,6 +96,11 @@ export enum CraftingSchema_Material_MaterialType { OPENVEX = 7, /** CSAF_VEX - https://docs.oasis-open.org/csaf/csaf/v2.0/cs03/csaf-v2.0-cs03.html */ CSAF_VEX = 8, + /** + * SARIF - Static analysis output format + * https://github.com/microsoft/sarif-tutorials/blob/main/docs/1-Introduction.md + */ + SARIF = 9, UNRECOGNIZED = -1, } @@ -128,6 +133,9 @@ export function craftingSchema_Material_MaterialTypeFromJSON(object: any): Craft case 8: case "CSAF_VEX": return CraftingSchema_Material_MaterialType.CSAF_VEX; + case 9: + case "SARIF": + return CraftingSchema_Material_MaterialType.SARIF; case -1: case "UNRECOGNIZED": default: @@ -155,6 +163,8 @@ export function craftingSchema_Material_MaterialTypeToJSON(object: CraftingSchem return "OPENVEX"; case CraftingSchema_Material_MaterialType.CSAF_VEX: return "CSAF_VEX"; + case CraftingSchema_Material_MaterialType.SARIF: + return "SARIF"; 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 077f15cfc..0b782cf74 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go @@ -101,7 +101,10 @@ const ( // https://github.com/openvex/spec CraftingSchema_Material_OPENVEX CraftingSchema_Material_MaterialType = 7 // https://docs.oasis-open.org/csaf/csaf/v2.0/cs03/csaf-v2.0-cs03.html - CraftingSchema_Material_CSAF_VEX CraftingSchema_Material_MaterialType = 8 // SARIF = 5; + 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 ) // Enum value maps for CraftingSchema_Material_MaterialType. @@ -116,6 +119,7 @@ var ( 6: "JUNIT_XML", 7: "OPENVEX", 8: "CSAF_VEX", + 9: "SARIF", } CraftingSchema_Material_MaterialType_value = map[string]int32{ "MATERIAL_TYPE_UNSPECIFIED": 0, @@ -127,6 +131,7 @@ var ( "JUNIT_XML": 6, "OPENVEX": 7, "CSAF_VEX": 8, + "SARIF": 9, } ) @@ -435,7 +440,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, 0x17, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, - 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbb, 0x07, 0x0a, 0x0e, 0x43, 0x72, + 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc6, 0x07, 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, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x0a, 0x02, 0x76, 0x31, 0x52, @@ -467,7 +472,7 @@ var file_workflowcontract_v1_crafting_schema_proto_rawDesc = []byte{ 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x47, 0x49, 0x54, 0x48, 0x55, 0x42, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x47, 0x49, 0x54, 0x4c, 0x41, 0x42, 0x5f, 0x50, 0x49, 0x50, 0x45, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x41, 0x5a, - 0x55, 0x52, 0x45, 0x5f, 0x50, 0x49, 0x50, 0x45, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x03, 0x1a, 0xb6, + 0x55, 0x52, 0x45, 0x5f, 0x50, 0x49, 0x50, 0x45, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x03, 0x1a, 0xc1, 0x03, 0x0a, 0x08, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x57, 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, @@ -484,7 +489,7 @@ var file_workflowcontract_v1_crafting_schema_proto_rawDesc = []byte{ 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, 0xb3, 0x01, 0x0a, 0x0c, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x54, 0x79, + 0x73, 0x22, 0xbe, 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, 0x10, 0x01, 0x12, 0x13, 0x0a, @@ -495,17 +500,18 @@ var file_workflowcontract_v1_crafting_schema_proto_rawDesc = []byte{ 0x4d, 0x5f, 0x53, 0x50, 0x44, 0x58, 0x5f, 0x4a, 0x53, 0x4f, 0x4e, 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, 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, 0xfa, 0x42, 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, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, - 0x4d, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, - 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x66, - 0x6c, 0x6f, 0x77, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2f, 0x76, 0x31, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 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, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x22, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0e, + 0xfa, 0x42, 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, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x4d, 0x5a, 0x4b, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, + 0x6f, 0x70, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, + 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/app/controlplane/api/workflowcontract/v1/crafting_schema.proto b/app/controlplane/api/workflowcontract/v1/crafting_schema.proto index 2eddf6cdd..f6f2a2ed5 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema.proto +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema.proto @@ -71,6 +71,9 @@ message CraftingSchema { OPENVEX = 7; // https://docs.oasis-open.org/csaf/csaf/v2.0/cs03/csaf-v2.0-cs03.html CSAF_VEX = 8; + // Static analysis output format + // https://github.com/microsoft/sarif-tutorials/blob/main/docs/1-Introduction.md + SARIF = 9; } } } diff --git a/go.mod b/go.mod index 13f8845ee..a09d6cf1f 100644 --- a/go.mod +++ b/go.mod @@ -214,6 +214,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/runc v1.1.9 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/owenrumney/go-sarif v1.1.1 github.com/pelletier/go-toml/v2 v2.0.9 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 86b619579..00ef9ad5b 100644 --- a/go.sum +++ b/go.sum @@ -957,6 +957,8 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/owenrumney/go-sarif v1.1.1 h1:QNObu6YX1igyFKhdzd7vgzmw7XsWN3/6NMGuDzBgXmE= +github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU= github.com/package-url/packageurl-go v0.1.1/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= @@ -1205,6 +1207,7 @@ github.com/zalando/go-keyring v0.2.2 h1:f0xmpYiSrHtSNAVgwip93Cg8tuF45HJM6rHq/A5R github.com/zalando/go-keyring v0.2.2/go.mod h1:sI3evg9Wvpw3+n4SqplGSJUMwtDeROfD4nsFz4z9PG0= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty v1.13.3 h1:m+b9q3YDbg6Bec5rr+KGy1MzEVzY/jC2X+YX4yqKtHI= github.com/zclconf/go-cty v1.13.3/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= diff --git a/internal/attestation/crafter/materials/materials.go b/internal/attestation/crafter/materials/materials.go index c0764f768..75c688319 100644 --- a/internal/attestation/crafter/materials/materials.go +++ b/internal/attestation/crafter/materials/materials.go @@ -160,6 +160,8 @@ func Craft(ctx context.Context, materialSchema *schemaapi.CraftingSchema_Materia crafter, err = NewOpenVEXCrafter(materialSchema, casBackend, logger) case schemaapi.CraftingSchema_Material_CSAF_VEX: crafter, err = NewCSAFVEXCrafter(materialSchema, casBackend, logger) + case schemaapi.CraftingSchema_Material_SARIF: + crafter, err = NewSARIFCrafter(materialSchema, casBackend, logger) default: return nil, fmt.Errorf("material of type %q not supported yet", materialSchema.Type) } diff --git a/internal/attestation/crafter/materials/sarif.go b/internal/attestation/crafter/materials/sarif.go new file mode 100644 index 000000000..1275d6624 --- /dev/null +++ b/internal/attestation/crafter/materials/sarif.go @@ -0,0 +1,58 @@ +// +// Copyright 2023 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 ( + "context" + "fmt" + + api "github.com/chainloop-dev/chainloop/app/cli/api/attestation/v1" + schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + "github.com/chainloop-dev/chainloop/internal/casclient" + "github.com/owenrumney/go-sarif/sarif" + "github.com/rs/zerolog" +) + +type SARIFCrafter struct { + backend *casclient.CASBackend + *crafterCommon +} + +func NewSARIFCrafter(materialSchema *schemaapi.CraftingSchema_Material, backend *casclient.CASBackend, l *zerolog.Logger) (*SARIFCrafter, error) { + if materialSchema.Type != schemaapi.CraftingSchema_Material_SARIF { + return nil, fmt.Errorf("material type is not SARIF format") + } + + return &SARIFCrafter{ + backend: backend, + crafterCommon: &crafterCommon{logger: l, input: materialSchema}, + }, nil +} + +func (i *SARIFCrafter) Craft(ctx context.Context, filepath string) (*api.Attestation_Material, error) { + i.logger.Debug().Str("path", filepath).Msg("decoding SARIF file") + doc, err := sarif.Open(filepath) + // parse doesn't fail if the provided file is a valid JSON, but not a valid CSAF VEX file + if err != nil || doc.Schema == "" { + if err != nil { + i.logger.Debug().Err(err).Msg("error decoding file") + } + + return nil, fmt.Errorf("invalid SARIF file: %w", ErrInvalidMaterialType) + } + + return uploadAndCraft(ctx, i.input, i.backend, filepath, i.logger) +} diff --git a/internal/attestation/crafter/materials/sarif_test.go b/internal/attestation/crafter/materials/sarif_test.go new file mode 100644 index 000000000..ed4a84f8e --- /dev/null +++ b/internal/attestation/crafter/materials/sarif_test.go @@ -0,0 +1,132 @@ +// +// Copyright 2023 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. + +//nolint:dupl +package materials_test + +import ( + "context" + "testing" + + attestationApi "github.com/chainloop-dev/chainloop/app/cli/api/attestation/v1" + contractAPI "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/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 TestNewSARIFCrafter(t *testing.T) { + testCases := []struct { + name string + input *contractAPI.CraftingSchema_Material + wantErr bool + }{ + { + name: "happy path", + input: &contractAPI.CraftingSchema_Material{ + Type: contractAPI.CraftingSchema_Material_SARIF, + }, + }, + { + 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.NewSARIFCrafter(tc.input, nil, nil) + if tc.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + }) + } +} + +func TestSARIFCraft(t *testing.T) { + testCases := []struct { + name string + filePath string + wantErr string + }{ + { + name: "non-expected json file", + filePath: "./testdata/sbom.cyclonedx.json", + wantErr: "unexpected material type", + }, + { + name: "invalid path", + filePath: "./testdata/non-existing.json", + wantErr: "unexpected material type", + }, + { + name: "invalid artifact type", + filePath: "./testdata/simple.txt", + wantErr: "unexpected material type", + }, + { + name: "valid artifact type", + filePath: "./testdata/report.sarif", + }, + } + + assert := assert.New(t) + schema := &contractAPI.CraftingSchema_Material{ + Name: "test", + Type: contractAPI.CraftingSchema_Material_SARIF, + } + 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{ + Digest: "deadbeef", + Filename: "report.sarif", + }, nil) + } + + backend := &casclient.CASBackend{Uploader: uploader} + crafter, err := materials.NewSARIFCrafter(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_SARIF.String(), got.MaterialType.String()) + assert.True(got.UploadedToCas) + + // // The result includes the digest reference + assert.Equal(&attestationApi.Attestation_Material_Artifact{ + Id: "test", Digest: "sha256:c4a63494f9289dd9fd44f841efb4f5b52765c2de6332f2d86e5f6c0340b40a95", Name: "report.sarif", + }, got.GetArtifact()) + }) + } +} diff --git a/internal/attestation/crafter/materials/testdata/report.sarif b/internal/attestation/crafter/materials/testdata/report.sarif new file mode 100644 index 000000000..e28c985ec --- /dev/null +++ b/internal/attestation/crafter/materials/testdata/report.sarif @@ -0,0 +1,313 @@ +{ + "version": "2.1.0", + "$schema": "https://json.schemastore.org/sarif-2.1.0.json", + "runs": [ + { + "tool": { + "driver": { + "informationUri": "https://tfsec.dev", + "name": "tfsec", + "rules": [ + { + "id": "AWS006", + "shortDescription": { + "text": "Resource 'aws_security_group_rule.my-rule' defines a fully open ingress security group rule." + }, + "helpUri": "See https://tfsec.dev/docs/aws/AWS006/ for more information.", + "help": { + "markdown": "# markdown" + }, + "properties": { + "impact": "Your port exposed to the internet", + "resolution": "Set a more restrictive cidr range" + } + }, + { + "id": "AZU003", + "shortDescription": { + "text": "Resource 'azurerm_managed_disk.source' defines an unencrypted managed disk." + }, + "helpUri": "See https://tfsec.dev/docs/azure/AZU003/ for more information.", + "help": { + "markdown": "# markdown" + }, + "properties": { + "impact": "", + "resolution": "" + } + }, + { + "id": "AWS025", + "shortDescription": { + "text": "Resource 'aws_api_gateway_domain_name.outdated_security_policy' defines outdated SSL/TLS policies (not using TLS_1_2)." + }, + "helpUri": "See https://tfsec.dev/docs/aws/AWS025/ for more information.", + "help": { + "markdown": "# markdown" + }, + "properties": { + "impact": "Outdated SSL policies increase exposure to known vulnerabilites", + "resolution": "Use the most modern TLS/SSL policies available" + } + }, + { + "id": "AWS018", + "shortDescription": { + "text": "Resource 'aws_security_group_rule.my-rule' should include a description for auditing purposes." + }, + "helpUri": "See https://tfsec.dev/docs/aws/AWS018/ for more information.", + "help": { + "markdown": "# markdown" + }, + "properties": { + "impact": "Descriptions provide context for the firewall rule reasons", + "resolution": "Add descriptions for all security groups anf rules" + } + }, + { + "id": "AWS004", + "shortDescription": { + "text": "Resource 'aws_alb_listener.my-alb-listener' uses plain HTTP instead of HTTPS." + }, + "helpUri": "See https://tfsec.dev/docs/aws/AWS004/ for more information.", + "help": { + "markdown": "# markdown" + }, + "properties": { + "impact": "Your traffic is not protected", + "resolution": "Switch to HTTPS to benefit from TLS security features" + } + }, + { + "id": "AWS003", + "shortDescription": { + "text": "Resource 'aws_db_security_group.my-group' uses EC2 Classic. Use a VPC instead." + }, + "helpUri": "See https://tfsec.dev/docs/aws/AWS003/ for more information.", + "help": { + "markdown": "# markdown" + }, + "properties": { + "impact": "Classic resources are running in a shared environment with other customers", + "resolution": "Switch to VPC resources" + } + }, + { + "id": "AWS092", + "shortDescription": { + "text": "Resource 'aws_dynamodb_table.bad_example' is not using KMS CMK for encryption" + }, + "helpUri": "See https://tfsec.dev/docs/aws/AWS092/ for more information.", + "help": { + "markdown": "# markdown" + }, + "properties": { + "impact": "Using AWS managed keys does not allow for fine grained control", + "resolution": "Enable server side encrytion with a customer managed key" + } + } + ] + } + }, + "artifacts": [ + { + "location": { + "uri": "/home/billybob/supertfsec/example/main.tf" + }, + "length": -1 + } + ], + "results": [ + { + "ruleId": "AWS006", + "ruleIndex": 0, + "level": "warning", + "message": { + "text": "Resource 'aws_security_group_rule.my-rule' defines a fully open ingress security group rule." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "/home/billybob/supertfsec/example/main.tf" + }, + "region": { + "startLine": 4, + "endLine": 4 + } + } + } + ] + }, + { + "ruleId": "AZU003", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Resource 'azurerm_managed_disk.source' defines an unencrypted managed disk." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "/home/billybob/supertfsec/example/main.tf" + }, + "region": { + "startLine": 22, + "endLine": 22 + } + } + } + ] + }, + { + "ruleId": "AWS025", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Resource 'aws_api_gateway_domain_name.missing_security_policy' should include security_policy (defauls to outdated SSL/TLS policy)." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "/home/billybob/supertfsec/example/main.tf" + }, + "region": { + "startLine": 26, + "endLine": 27 + } + } + } + ] + }, + { + "ruleId": "AWS025", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Resource 'aws_api_gateway_domain_name.empty_security_policy' defines outdated SSL/TLS policies (not using TLS_1_2)." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "/home/billybob/supertfsec/example/main.tf" + }, + "region": { + "startLine": 30, + "endLine": 30 + } + } + } + ] + }, + { + "ruleId": "AWS025", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Resource 'aws_api_gateway_domain_name.outdated_security_policy' defines outdated SSL/TLS policies (not using TLS_1_2)." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "/home/billybob/supertfsec/example/main.tf" + }, + "region": { + "startLine": 34, + "endLine": 34 + } + } + } + ] + }, + { + "ruleId": "AWS018", + "ruleIndex": 3, + "level": "error", + "message": { + "text": "Resource 'aws_security_group_rule.my-rule' should include a description for auditing purposes." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "/home/billybob/supertfsec/example/main.tf" + }, + "region": { + "startLine": 2, + "endLine": 5 + } + } + } + ] + }, + { + "ruleId": "AWS004", + "ruleIndex": 4, + "level": "error", + "message": { + "text": "Resource 'aws_alb_listener.my-alb-listener' uses plain HTTP instead of HTTPS." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "/home/billybob/supertfsec/example/main.tf" + }, + "region": { + "startLine": 9, + "endLine": 9 + } + } + } + ] + }, + { + "ruleId": "AWS003", + "ruleIndex": 5, + "level": "error", + "message": { + "text": "Resource 'aws_db_security_group.my-group' uses EC2 Classic. Use a VPC instead." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "/home/billybob/supertfsec/example/main.tf" + }, + "region": { + "startLine": 12, + "endLine": 14 + } + } + } + ] + }, + { + "ruleId": "AWS092", + "ruleIndex": 6, + "level": "warning", + "message": { + "text": "Resource 'aws_dynamodb_table.bad_example' is not using KMS CMK for encryption" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "/home/billybob/supertfsec/example/main.tf" + }, + "region": { + "startLine": 41, + "endLine": 56 + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file