Skip to content

Commit

Permalink
Merge pull request #2 from smithy-security/andream16/OCU-277/support-…
Browse files Browse the repository at this point in the history
…openapi-spec-for-sarif

Generating sarif bindings.
  • Loading branch information
andream16 authored Dec 3, 2024
2 parents 3d49463 + 89559ba commit 281ad13
Show file tree
Hide file tree
Showing 18 changed files with 17,204 additions and 0 deletions.
8 changes: 8 additions & 0 deletions sarif/Dockerfile.gojsonschema
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM golang:1.23-alpine

RUN apk add --no-cache ca-certificates
RUN go install github.com/atombender/[email protected]

WORKDIR /app

ENTRYPOINT ["go-jsonschema"]
25 changes: 25 additions & 0 deletions sarif/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.PHONY: build-image generate-schemas

# Schema versions.
VERSIONS := v2-1-0 v2-2-0
# Base paths.
SPEC_DIR := spec/contracts/sarif-schema
GEN_DIR := gen/sarif-schema

build-image:
@echo "Building the Docker image..."
docker build \
--platform linux/amd64 \
-t gojsonschema . -f Dockerfile.gojsonschema

generate-schemas: build-image
@for version in $(VERSIONS); do \
echo "Generating schema for $$version..."; \
rm -rf $(GEN_DIR)/$$version; \
mkdir -p $(GEN_DIR)/$$version; \
docker run \
--platform linux/amd64 \
-v ./spec:/app/spec \
-it gojsonschema:latest \
-p schema $(SPEC_DIR)/$$version/schema.json > $(GEN_DIR)/$$version/schema.go; \
done
66 changes: 66 additions & 0 deletions sarif/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# sarif

Utilities for [sarif](https://sarifweb.azurewebsites.net/) that leverage generated code for
[sarif-spec](https://github.com/oasis-tcs/sarif-spec/tree/main).

## Why?

Other packages are not well maintained and don't leverage generated code.

This means that updates to the specification are not often backported into the packages.

## How to use

### V2.1.0
For [v2.1.0](https://github.com/oasis-tcs/sarif-spec/tree/main/sarif-2.1):

```go
package main

import (
"log"

schemav1 "github.com/smithy-security/pkg/sarif/spec/gen/sarif-schema/v2-1-0"
)

//go:embed testdata/gosec_v2.1.0.json
var reportV2_1_0 []byte

func main() {
report := schemav1.SchemaJson{}
if err := report.UnmarshalJSON(reportV2_1_0); err != nil {
log.Fatalf("report unmarshalling failed: %v", err)
}
}
```

### V2.2.0
For [v2.2.0](https://github.com/oasis-tcs/sarif-spec/tree/main/sarif-2.2):

```go
package main

import (
"log"

schemav2 "github.com/smithy-security/pkg/sarif/spec/gen/sarif-schema/v2-2-0"
)

//go:embed testdata/gosec_v2.2.0.json
var reportV2_2_0 []byte

func main() {
report := schemav2.SchemaJson{}
if err := report.UnmarshalJSON(reportV2_2_0); err != nil {
log.Fatalf("report unmarshalling failed: %v", err)
}
}
```

## Generate code

To generate the code from the jsonschema specs, please run:

```shell
make generate-schemas
```
5 changes: 5 additions & 0 deletions sarif/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/smithy-security/pkg/sarif

go 1.23.2

require github.com/mitchellh/mapstructure v1.5.0
2 changes: 2 additions & 0 deletions sarif/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
212 changes: 212 additions & 0 deletions sarif/sarif_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
package sarif_test

import (
_ "embed"
"testing"

schemav1 "github.com/smithy-security/pkg/sarif/spec/gen/sarif-schema/v2-1-0"
)

var (
//go:embed testdata/gosec_v2.1.0.json
reportV2_1_0 []byte
)

func TestReportFromBytesV2_1_0(t *testing.T) {
const (
expectedNumOfRuns = 1
expectedNumResults = 21
expectedNumTaxonomies = 1
expectedNumTaxas = 12
expectedNumDriverRules = 15
)

report := schemav1.SchemaJson{}
if err := report.UnmarshalJSON(reportV2_1_0); err != nil {
t.Fatalf("report unmarshalling failed: %v", err)
}

switch {
case *report.Schema != "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json":
t.Fatalf("unexpected schema '%s'", *report.Schema)
case report.Version != "2.1.0":
t.Fatalf("Expected version '2.1.0', got '%s'", report.Version)
case expectedNumOfRuns != len(report.Runs):
t.Fatalf("expected %d runs. Expected %d", len(report.Runs), expectedNumOfRuns)
case expectedNumResults != len(report.Runs[0].Results):
t.Fatalf("expected %d results. Expected %d", len(report.Runs[0].Results), expectedNumResults)
case expectedNumTaxonomies != len(report.Runs[0].Taxonomies):
t.Fatalf("expected %d taxonomies. Expected %d", len(report.Runs[0].Taxonomies), expectedNumTaxonomies)
}

run := report.Runs[0]

for _, res := range run.Results {
switch {
case res.Level != "error" && res.Level != "warning":
t.Fatalf("Expected level 'error', got '%s'", res.Level)
case *res.Message.Text == "":
t.Fatal("Expected message text to not be empty")
case *res.RuleId == "":
t.Fatal("Expected rule id to not be empty")
}

for _, loc := range res.Locations {
if loc.PhysicalLocation == nil {
t.Fatal("Expected physical location not to be nil")
}

switch {
case loc.PhysicalLocation.ArtifactLocation == nil:
t.Fatal("Expected PhysicalLocation ArtifactLocation to not be nil")
case loc.PhysicalLocation.ArtifactLocation.Uri == nil || *loc.PhysicalLocation.ArtifactLocation.Uri == "":
t.Fatalf("Expected PhysicalLocation ArtifactLocation Uri to not be empty")
case loc.PhysicalLocation.Region == nil:
t.Fatal("Expected PhysicalLocation ArtifactLocation Region to not be empty")
case loc.PhysicalLocation.Region.EndColumn == nil:
t.Fatal("Expected PhysicalLocation ArtifactLocation Region EndColumn to not be empty")
case loc.PhysicalLocation.Region.StartColumn == nil:
t.Fatal("Expected PhysicalLocation ArtifactLocation Region StartColumn to not be empty")
case loc.PhysicalLocation.Region.EndLine == nil:
t.Fatal("Expected PhysicalLocation ArtifactLocation Region EndLine to not be empty")
case loc.PhysicalLocation.Region.StartLine == nil:
t.Fatal("Expected PhysicalLocation ArtifactLocation Region StartLine to not be empty")
case loc.PhysicalLocation.Region.Snippet == nil:
t.Fatal("Expected PhysicalLocation ArtifactLocation Region Snippet to not be empty")
case loc.PhysicalLocation.Region.Snippet.Text == nil || *loc.PhysicalLocation.Region.Snippet.Text == "":
t.Fatal("Expected PhysicalLocation ArtifactLocation Region Snippet Text to not be empty")
}
}
}

for _, taxonomy := range run.Taxonomies {
switch {
case taxonomy.DownloadUri == nil || *taxonomy.DownloadUri == "":
t.Fatal("Expected taxonomy URI to not be empty")
case taxonomy.Guid == nil || *taxonomy.Guid == "":
t.Fatal("Expected taxonomy Guid to not be empty")
case taxonomy.InformationUri == nil || *taxonomy.InformationUri == "":
t.Fatal("Expected taxonomy InformationUri to not be empty")
case taxonomy.MinimumRequiredLocalizedDataSemanticVersion == nil || *taxonomy.MinimumRequiredLocalizedDataSemanticVersion == "":
t.Fatal("Expected taxonomy MinimumRequiredLocalizedDataSemanticVersion to not be empty")
case !taxonomy.IsComprehensive:
t.Fatal("Expected taxonomy to be comprehensive")
case taxonomy.Language == "":
t.Fatal("Expected taxonomy Language to not be empty")
case taxonomy.Name == "":
t.Fatal("Expected taxonomy Name to not be empty")
case taxonomy.Organization == nil || *taxonomy.Organization == "":
t.Fatal("Expected taxonomy Organization to not be empty")
case taxonomy.ReleaseDateUtc == nil || *taxonomy.ReleaseDateUtc == "":
t.Fatal("Expected taxonomy ReleaseDateUtc to not be empty")
case taxonomy.ShortDescription == nil:
t.Fatal("Expected taxonomy ShortDescription to not be nil")
case taxonomy.ShortDescription.Text == "":
t.Fatal("Expected taxonomy ShortDescription Text to not be nil")
case taxonomy.Version == nil || *taxonomy.Version == "":
t.Fatal("Expected taxonomy Version to not be empty")
case expectedNumTaxas != len(taxonomy.Taxa):
t.Fatalf("Expected %d taxonomy taxas. Found %d instead", expectedNumTaxas, len(taxonomy.Taxa))
}

for _, taxa := range taxonomy.Taxa {
switch {
case taxa.FullDescription == nil:
t.Fatal("Expected taxa FullDescription to not be nil")
case taxa.FullDescription.Text == "":
t.Fatal("Expected taxa FullDescription Text to not be empty")
case taxa.Guid == nil || *taxa.Guid == "":
t.Fatal("Expected taxa Guid to not be empty")
case taxa.HelpUri == nil || *taxa.HelpUri == "":
t.Fatal("Expected taxa HelpUri to not be empty")
case taxa.ShortDescription == nil:
t.Fatal("Expected taxa ShortDescription to not be nil")
case taxa.ShortDescription.Text == "":
t.Fatal("Expected taxa ShortDescription Text to not be nil")
case taxa.Id == "":
t.Fatal("Expected taxa Id to not be empty")
}
}
}

driver := run.Tool.Driver
switch {
case driver.Guid == nil || *driver.Guid == "":
t.Fatal("Expected Driver Guid to not be empty")
case driver.InformationUri == nil || *driver.InformationUri == "":
t.Fatal("Expected Driver InformationUri to not be empty")
case driver.SemanticVersion == nil || *driver.SemanticVersion == "":
t.Fatal("Expected Driver SemanticVersion to not be empty")
case driver.Version == nil || *driver.Version == "":
t.Fatal("Expected Driver Version to not be empty")
case driver.Name == "":
t.Fatal("Expected Driver Name to not be empty")
case len(driver.SupportedTaxonomies) != 1:
t.Fatalf("expected 1 Driver SupportedTaxonomy. Got %d instead", len(driver.SupportedTaxonomies))
case len(driver.Rules) != expectedNumDriverRules:
t.Fatalf("expected 1 Driver Rules. Got %d instead", len(driver.Rules))
}

for _, rule := range driver.Rules {
switch {
case rule.DefaultConfiguration == nil:
t.Fatal("Expected rule DefaultConfiguration to not be nil")
case rule.FullDescription == nil:
t.Fatal("Expected rule FullDescription to not be nil")
case rule.Help == nil:
t.Fatal("Expected rule Help to not be nil")
case rule.Properties == nil:
t.Fatal("Expected rule Properties to not be nil")
case rule.ShortDescription == nil:
t.Fatal("Expected rule ShortDescription to not be nil")
case len(rule.Relationships) == 0:
t.Fatal("Expected rule Relationships to not be empty")
case rule.DefaultConfiguration.Level == "":
t.Fatal("Expected DefaultConfiguration Level to not be empty")
case rule.FullDescription.Text == "":
t.Fatal("Expected FullDescription Text to not be empty")
case rule.Help.Text == "":
t.Fatal("Expected Help Text to not be empty")
case rule.Id == "":
t.Fatal("Expected rule Id to not be empty")
case rule.Name == nil || *rule.Name == "":
t.Fatal("Expected rule Name to not be empty")
case rule.ShortDescription.Text == "":
t.Fatal("Expected rule ShortDescription Text to not be empty")
case rule.Properties.AdditionalProperties == nil:
t.Fatal("Expected rule Properties AdditionalProperties to not be nil")
case len(rule.Properties.Tags) != 2:
t.Fatal("Expected rule Properties Tags to have 2 elements")
}

props, ok := rule.Properties.AdditionalProperties.(map[string]any)
if !ok {
t.Fatal("Expected rule Properties AdditionalProperties to be a map")
}

precision, ok := props["precision"]
switch {
case !ok:
t.Fatal("Expected rule Precision to be found in properties")
case precision == "":
t.Fatal("Expected rule Precision to not be empty")
}

for _, rel := range rule.Relationships {
switch {
case rel.Target.Id == nil || *rel.Target.Id == "":
t.Fatal("Expected rule Target Id to not be empty")
case rel.Target.Guid == nil || *rel.Target.Guid == "":
t.Fatal("Expected rule Target Guid to not be empty")
case rel.Target.ToolComponent == nil:
t.Fatal("Expected rule Target ToolComponent to not be nil")
case rel.Target.ToolComponent.Guid == nil:
t.Fatal("Expected rule Target ToolComponent Guid to not be empty")
case rel.Target.ToolComponent.Name == nil || *rel.Target.ToolComponent.Name == "":
t.Fatal("Expected rule Target ToolComponent Name to not be empty")
case len(rel.Kinds) == 0:
t.Fatal("Expected rule Kind to not be empty")
}
}
}
}
Loading

0 comments on commit 281ad13

Please sign in to comment.