diff --git a/CLAUDE.md b/CLAUDE.md index 0d1afff10..c99e14b15 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -264,4 +264,5 @@ Code reviews are required for all submissions via GitHub pull requests. - For each file you modify, update the license header. If it says 2024, change it to 2024-2025. If there's no license header, create one with the current year. - if you add any new dependency to a constructor, remember to run wire ./... - when creating PR message, keep it high-level, what functionality was added, don't add info about testing, no icons, no info about how the message was generated. -- app/controlplane/api/gen/frontend/google/protobuf/descriptor.ts is a special case that we don't want to upgrade, so if it upgrades, put it back to main \ No newline at end of file +- app/controlplane/api/gen/frontend/google/protobuf/descriptor.ts is a special case that we don't want to upgrade, so if it upgrades, put it back to main +- when creating a commit or PR message, NEVER add co-authored by or generated by Claude code \ No newline at end of file diff --git a/pkg/attestation/crafter/materials/gitlab.go b/pkg/attestation/crafter/materials/gitlab.go index cd8f41b82..e97b2f519 100644 --- a/pkg/attestation/crafter/materials/gitlab.go +++ b/pkg/attestation/crafter/materials/gitlab.go @@ -45,27 +45,48 @@ func NewGitlabCrafter(schema *schemaapi.CraftingSchema_Material, backend *cascli } func (i *GitlabCrafter) Craft(ctx context.Context, filePath string) (*api.Attestation_Material, error) { - if err := i.validate(filePath); err != nil { - return nil, err - } - - return uploadAndCraft(ctx, i.input, i.backend, filePath, i.logger) -} - -func (i *GitlabCrafter) validate(filePath string) error { data, err := os.ReadFile(filePath) if err != nil { - return fmt.Errorf("can't open the file: %w", err) + return nil, fmt.Errorf("can't open the file: %w", err) } var glReport report.Report if err = json.Unmarshal(data, &glReport); err != nil { - return fmt.Errorf("error unmarshalling report file: %w", err) + return nil, fmt.Errorf("error unmarshalling report file: %w", err) } if !slices.Contains(supportedTypes, string(glReport.Scan.Type)) { - return fmt.Errorf("error loading Gitlab report. Missing scan type") + return nil, fmt.Errorf("error loading Gitlab report. Missing scan type") + } + + m, err := uploadAndCraft(ctx, i.input, i.backend, filePath, i.logger) + if err != nil { + return nil, err + } + + i.injectAnnotations(m, &glReport) + + return m, nil +} + +func (i *GitlabCrafter) injectAnnotations(m *api.Attestation_Material, glReport *report.Report) { + // Prefer scanner (the actual security tool) over analyzer (the wrapper/integration layer) + toolName := glReport.Scan.Scanner.Name + toolVersion := glReport.Scan.Scanner.Version + + // Fallback to analyzer if scanner information is not available + if toolName == "" { + toolName = glReport.Scan.Analyzer.Name + toolVersion = glReport.Scan.Analyzer.Version } - return nil + if toolName != "" { + if m.Annotations == nil { + m.Annotations = make(map[string]string) + } + m.Annotations[AnnotationToolNameKey] = toolName + if toolVersion != "" { + m.Annotations[AnnotationToolVersionKey] = toolVersion + } + } } diff --git a/pkg/attestation/crafter/materials/gitlab_test.go b/pkg/attestation/crafter/materials/gitlab_test.go index d81eefca0..6a9879614 100644 --- a/pkg/attestation/crafter/materials/gitlab_test.go +++ b/pkg/attestation/crafter/materials/gitlab_test.go @@ -64,9 +64,10 @@ func TestNewGitlabCrafter(t *testing.T) { func TestGitlabCrafter_Craft(t *testing.T) { testCases := []struct { - name string - filePath string - wantErr string + name string + filePath string + wantErr string + annotations map[string]string }{ { name: "invalid path", @@ -86,14 +87,34 @@ func TestGitlabCrafter_Craft(t *testing.T) { { name: "sast report", filePath: "./testdata/gl-sast-report.json", + annotations: map[string]string{ + "chainloop.material.tool.name": "Semgrep", + "chainloop.material.tool.version": ":SKIP:", + }, }, { name: "container scanning report", filePath: "./testdata/gl-container-scanning-report.json", + annotations: map[string]string{ + "chainloop.material.tool.name": "Trivy", + "chainloop.material.tool.version": "0.19.2", + }, }, { name: "secret detection report", filePath: "./testdata/gl-secret-detection-report.json", + annotations: map[string]string{ + "chainloop.material.tool.name": "Gitleaks", + "chainloop.material.tool.version": ":SKIP:", + }, + }, + { + name: "sonarqube report", + filePath: "./testdata/gl-sonarqube-report.json", + annotations: map[string]string{ + "chainloop.material.tool.name": "Sonar", + "chainloop.material.tool.version": "11.2.0.2797", + }, }, } @@ -125,6 +146,12 @@ func TestGitlabCrafter_Craft(t *testing.T) { require.NoError(t, err) assert.Equal(t, contractAPI.CraftingSchema_Material_GITLAB_SECURITY_REPORT.String(), got.MaterialType.String()) assert.True(t, got.UploadedToCas) + + if tc.annotations != nil { + for k, v := range tc.annotations { + assert.Equal(t, v, got.Annotations[k]) + } + } }) } } diff --git a/pkg/attestation/crafter/materials/testdata/gl-sonarqube-report.json b/pkg/attestation/crafter/materials/testdata/gl-sonarqube-report.json new file mode 100644 index 000000000..6b1651b3a --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/gl-sonarqube-report.json @@ -0,0 +1,27 @@ +{ + "version": "15.0.0", + "scan": { + "analyzer": { + "id": "SONARQUBE 2025.1.1.104738", + "name": "SONARQUBE - Enterprise", + "vendor": { + "name": "Sonar" + }, + "version": "2025.1.1.104738" + }, + "scanner": { + "id": "sonar_scan", + "name": "Sonar", + "vendor": { + "name": "Sonar" + }, + "version": "11.2.0.2797" + }, + "start_time": "2025-10-20T20:18:45", + "end_time": "2025-10-20T20:18:45", + "status": "success", + "messages": [], + "type": "sast" + }, + "vulnerabilities": [] +}