Ctrl K
ComunidadGuías…

internal/checks/sarif_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package checks

import (
	"encoding/json"
	"reflect"
	"strings"
	"testing"

	"github.com/sophotechlabs/spinoza/internal/api"
)

func sarifSample() api.CheckReport {
	return api.CheckReport{
		Objects: []api.CheckObject{
			{Group: "apps", Version: "v1", Resource: "deployments", Kind: "Deployment", Namespace: "prod", Name: "web"},
			{Group: "apps", Version: "v1", Resource: "deployments", Kind: "Deployment", Namespace: "prod", Name: "api"},
			{Version: "v1", Resource: "clusterroles", Kind: "ClusterRole", Name: "spinoza-reader"},
		},
		Groups: []api.CheckGroup{
			{
				ID:         "privileged-container",
				Title:      "Runs a privileged container",
				Category:   "security",
				Severity:   severityHigh,
				Frameworks: []string{"PSS baseline", "NSA/CISA"},
				Wrong:      "a privileged container holds every capability the node has",
				Remedy:     "drop privileged from the container's security context",
				Total:      2,
				Findings: []api.CheckFinding{
					{Ref: 0, Container: "app", Detail: "runs privileged", Severity: severityHigh},
					{
						Ref: 1, Container: "sidecar", Detail: "runs privileged", Severity: severityHigh,
						Muted: true, MutedBy: ScopeObject, Reason: "the node agent needs it",
					},
				},
			},
			{
				ID:       "requests-missing",
				Title:    "Sets no CPU or memory request",
				Category: "efficiency",
				Severity: severityLow,
				Wrong:    "the scheduler cannot place what does not say what it needs",
				Remedy:   "set requests on every container",
				Total:    1,
				Findings: []api.CheckFinding{
					{Ref: 2, Detail: "sets no requests", Severity: severityLow},
				},
			},
		},
	}
}

func sarifRuleNamed(t *testing.T, doc SARIFLog, id string) SARIFRule {
	t.Helper()
	for _, rule := range doc.Runs[0].Tool.Driver.Rules {
		if rule.ID == id {
			return rule
		}
	}
	t.Fatalf("no rule for %q among %d rules", id, len(doc.Runs[0].Tool.Driver.Rules))
	return SARIFRule{}
}

func sarifResultNamed(t *testing.T, doc SARIFLog, id, container string) SARIFResult {
	t.Helper()
	for _, result := range doc.Runs[0].Results {
		if result.RuleID != id {
			continue
		}
		if container == "" || strings.Contains(result.Message.Text, "container "+container) {
			return result
		}
	}
	t.Fatalf("no result for %q container %q among %d results", id, container, len(doc.Runs[0].Results))
	return SARIFResult{}
}

func TestTheSARIFRunSaysWhoWroteItAndWhatItIs(t *testing.T) {
	doc := SARIF(sarifSample())

	if doc.Version != "2.1.0" {
		t.Fatalf("version = %q, want 2.1.0", doc.Version)
	}
	if doc.Schema == "" {
		t.Fatal("the document named no schema")
	}
	if len(doc.Runs) != 1 {
		t.Fatalf("runs = %d, want 1", len(doc.Runs))
	}
	driver := doc.Runs[0].Tool.Driver
	if driver.Name != "spinoza" {
		t.Fatalf("driver name = %q", driver.Name)
	}
	if driver.InformationURI != "https://spinoza.tech" {
		t.Fatalf("informationUri = %q", driver.InformationURI)
	}
	if driver.Version == "" {
		t.Fatal("the driver did not say which build wrote the file")
	}
}

func TestTheSARIFExportCarriesOneRulePerCheckAndOneResultPerFinding(t *testing.T) {
	report := sarifSample()

	doc := SARIF(report)

	if len(doc.Runs[0].Tool.Driver.Rules) != len(report.Groups) {
		t.Fatalf("rules = %d, want %d", len(doc.Runs[0].Tool.Driver.Rules), len(report.Groups))
	}
	want := 0
	for _, group := range report.Groups {
		want += len(group.Findings)
	}
	if len(doc.Runs[0].Results) != want {
		t.Fatalf("results = %d, want %d", len(doc.Runs[0].Results), want)
	}
	for _, result := range doc.Runs[0].Results {
		sarifRuleNamed(t, doc, result.RuleID)
	}
}

func TestASARIFRuleCarriesWhatIsWrongAndTheRemedy(t *testing.T) {
	doc := SARIF(sarifSample())

	rule := sarifRuleNamed(t, doc, "privileged-container")
	if rule.Name != "privileged-container" {
		t.Fatalf("name = %q", rule.Name)
	}
	if rule.ShortDescription.Text != "Runs a privileged container" {
		t.Fatalf("shortDescription = %q", rule.ShortDescription.Text)
	}
	if !strings.Contains(rule.FullDescription.Text, "every capability") {
		t.Fatalf("fullDescription = %q", rule.FullDescription.Text)
	}
	if !strings.Contains(rule.Help.Text, "security context") {
		t.Fatalf("help = %q", rule.Help.Text)
	}
}

func TestASARIFRuleTagsTheCategoryAndEveryFramework(t *testing.T) {
	doc := SARIF(sarifSample())

	cases := []struct {
		name string
		id   string
		want []string
	}{
		{
			name: "a check carrying two frameworks",
			id:   "privileged-container",
			want: []string{"security", "PSS baseline", "NSA/CISA"},
		},
		{
			name: "a check carrying none",
			id:   "requests-missing",
			want: []string{"efficiency"},
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got := sarifRuleNamed(t, doc, tc.id).Properties.Tags
			if !reflect.DeepEqual(got, tc.want) {
				t.Fatalf("tags = %v, want %v", got, tc.want)
			}
		})
	}
}

func TestSARIFLevelsFollowTheSeverityTable(t *testing.T) {
	cases := []struct {
		name     string
		severity string
		level    string
		problem  string
	}{
		{name: "high is an error", severity: severityHigh, level: "error", problem: "error"},
		{name: "medium is a warning", severity: severityMedium, level: "warning", problem: "warning"},
		{name: "low is a note", severity: severityLow, level: "note", problem: "recommendation"},
		{name: "a severity nobody declared is none", severity: "moderate", level: "none", problem: "recommendation"},
		{name: "no severity at all is none", severity: "", level: "none", problem: "recommendation"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			report := api.CheckReport{
				Objects: []api.CheckObject{{Kind: "Pod", Namespace: "prod", Name: "web-0"}},
				Groups: []api.CheckGroup{{
					ID:       "some-check",
					Severity: tc.severity,
					Findings: []api.CheckFinding{{Ref: 0, Detail: "something is wrong"}},
				}},
			}

			doc := SARIF(report)

			rule := sarifRuleNamed(t, doc, "some-check")
			if rule.DefaultConfiguration.Level != tc.level {
				t.Fatalf("level = %q, want %q", rule.DefaultConfiguration.Level, tc.level)
			}
			if rule.Properties.Problem.Severity != tc.problem {
				t.Fatalf("problem.severity = %q, want %q", rule.Properties.Problem.Severity, tc.problem)
			}
			if doc.Runs[0].Results[0].Level != tc.level {
				t.Fatalf("result level = %q, want %q", doc.Runs[0].Results[0].Level, tc.level)
			}
		})
	}
}

func TestASARIFResultNamesTheObjectAndWhatIsWrong(t *testing.T) {
	doc := SARIF(sarifSample())

	result := sarifResultNamed(t, doc, "privileged-container", "app")
	if result.Message.Text != "Deployment prod/web container app: runs privileged" {
		t.Fatalf("message = %q", result.Message.Text)
	}
	if len(result.Locations) != 1 {
		t.Fatalf("locations = %d, want 1", len(result.Locations))
	}
	where := result.Locations[0].LogicalLocations[0]
	if where.FullyQualifiedName != "Deployment/prod/web" {
		t.Fatalf("fullyQualifiedName = %q", where.FullyQualifiedName)
	}
	if where.Name != "web" {
		t.Fatalf("logical location name = %q", where.Name)
	}
}

func TestASARIFResultOnAClusterScopedObjectStillSaysWhereItIs(t *testing.T) {
	doc := SARIF(sarifSample())

	result := sarifResultNamed(t, doc, "requests-missing", "")
	if result.Message.Text != "ClusterRole spinoza-reader: sets no requests" {
		t.Fatalf("message = %q", result.Message.Text)
	}
	if got := result.Locations[0].LogicalLocations[0].FullyQualifiedName; got != "ClusterRole/spinoza-reader" {
		t.Fatalf("fullyQualifiedName = %q", got)
	}
}

func TestTheSARIFExportInventsNoFileOrLine(t *testing.T) {
	body, err := json.Marshal(SARIF(sarifSample()))
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	if strings.Contains(string(body), "physicalLocation") {
		t.Fatal("the export made up a file and a line for a kubernetes object")
	}
	if !strings.Contains(string(body), "logicalLocations") {
		t.Fatal("the export said nothing about where the object is")
	}
}

func TestASARIFFingerprintIsTheSameOnASecondConversion(t *testing.T) {
	report := sarifSample()

	first := SARIF(report)
	second := SARIF(report)

	for at, result := range first.Runs[0].Results {
		got := second.Runs[0].Results[at].PartialFingerprints
		if !reflect.DeepEqual(result.PartialFingerprints, got) {
			t.Fatalf("result %d moved from %v to %v between runs", at, result.PartialFingerprints, got)
		}
		if len(result.PartialFingerprints) != 1 {
			t.Fatalf("result %d carried %d fingerprints", at, len(result.PartialFingerprints))
		}
	}
}

func TestASARIFFingerprintIsBlindToWhatChangesBetweenRuns(t *testing.T) {
	report := sarifSample()
	moved := sarifSample()
	moved.Groups[0].Findings[0].New = true
	moved.Groups[0].Total = 97
	moved.Groups[0].Findings[0].Detail = "runs privileged, seen again"
	moved.Baseline = "2026-09-09T10:00:00Z"

	before := SARIF(report).Runs[0].Results[0].PartialFingerprints
	after := SARIF(moved).Runs[0].Results[0].PartialFingerprints

	if !reflect.DeepEqual(before, after) {
		t.Fatalf("the fingerprint moved from %v to %v when only the run changed", before, after)
	}
}

func TestASARIFFingerprintDiffersForADifferentFinding(t *testing.T) {
	doc := SARIF(sarifSample())
	seen := map[string]string{}

	for _, result := range doc.Runs[0].Results {
		mark := result.PartialFingerprints[sarifPrintKey]
		if was, twice := seen[mark]; twice {
			t.Fatalf("%s and %s share the fingerprint %s", was, result.Message.Text, mark)
		}
		seen[mark] = result.Message.Text
	}
	if len(seen) != len(doc.Runs[0].Results) {
		t.Fatalf("%d fingerprints for %d results", len(seen), len(doc.Runs[0].Results))
	}
}

func TestAMutedFindingIsCarriedAsASARIFSuppression(t *testing.T) {
	doc := SARIF(sarifSample())

	muted := sarifResultNamed(t, doc, "privileged-container", "sidecar")
	if len(muted.Suppressions) != 1 {
		t.Fatalf("suppressions = %d, want 1", len(muted.Suppressions))
	}
	if muted.Suppressions[0].Kind != "external" {
		t.Fatalf("suppression kind = %q", muted.Suppressions[0].Kind)
	}
	if muted.Suppressions[0].Justification != "the node agent needs it" {
		t.Fatalf("justification = %q", muted.Suppressions[0].Justification)
	}
	live := sarifResultNamed(t, doc, "privileged-container", "app")
	if len(live.Suppressions) != 0 {
		t.Fatalf("a live finding was suppressed: %v", live.Suppressions)
	}
}

func TestASARIFResultWhoseObjectIsOutOfRangeDoesNotPanic(t *testing.T) {
	cases := []struct {
		name string
		ref  int
	}{
		{name: "a reference below the first object", ref: -1},
		{name: "a reference one past the last object", ref: 1},
		{name: "a reference far past the last object", ref: 4096},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			report := api.CheckReport{
				Objects: []api.CheckObject{{Kind: "Pod", Namespace: "prod", Name: "web-0"}},
				Groups: []api.CheckGroup{{
					ID:       "some-check",
					Severity: severityHigh,
					Findings: []api.CheckFinding{{Ref: tc.ref, Detail: "something is wrong"}},
				}},
			}

			doc := SARIF(report)

			if len(doc.Runs[0].Results) != 1 {
				t.Fatalf("results = %d, want 1", len(doc.Runs[0].Results))
			}
			result := doc.Runs[0].Results[0]
			if len(result.Locations) != 0 {
				t.Fatalf("locations = %v, want none for an object nobody can point at", result.Locations)
			}
			if !strings.Contains(result.Message.Text, "did not carry") {
				t.Fatalf("message = %q", result.Message.Text)
			}
			if result.PartialFingerprints[sarifPrintKey] == "" {
				t.Fatal("the result carried no fingerprint")
			}
		})
	}
}

func TestTheSARIFDocumentRoundTripsThroughJSON(t *testing.T) {
	doc := SARIF(sarifSample())

	body, err := json.Marshal(doc)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}
	var back SARIFLog
	if decodeErr := json.Unmarshal(body, &back); decodeErr != nil {
		t.Fatalf("unmarshal: %v", decodeErr)
	}

	if !reflect.DeepEqual(doc, back) {
		t.Fatalf("the document came back different:\n%+v\n%+v", doc, back)
	}
	var loose map[string]any
	if decodeErr := json.Unmarshal(body, &loose); decodeErr != nil {
		t.Fatalf("the document is not plain json: %v", decodeErr)
	}
	if loose["$schema"] == "" || loose["version"] != "2.1.0" {
		t.Fatalf("the encoded document read %v", loose["version"])
	}
}

func TestAnEmptySARIFReportIsStillAValidDocument(t *testing.T) {
	doc := SARIF(api.CheckReport{})

	body, err := json.Marshal(doc)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}
	if !strings.Contains(string(body), `"results":[]`) {
		t.Fatalf("an empty run did not carry an empty result list: %s", body)
	}
	if !strings.Contains(string(body), `"rules":[]`) {
		t.Fatalf("an empty run did not carry an empty rule list: %s", body)
	}
}

func TestSpinozaCanReadBackTheSARIFItWrote(t *testing.T) {
	doc := SARIF(sarifSample())
	written, err := json.Marshal(doc)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	tool, rules, readErr := readSARIF(written)
	if readErr != nil {
		t.Fatalf("the export did not read back: %v", readErr)
	}
	if tool != "spinoza" {
		t.Fatalf("the tool read back as %q", tool)
	}
	if len(rules) == 0 {
		t.Fatal("the export read back with no rules")
	}
	seen := map[string]bool{}
	for _, rule := range rules {
		for _, finding := range rule.findings {
			if finding.object.kind == "" || finding.object.name == "" {
				t.Fatalf("%s read back without an object: %+v", rule.id, finding.object)
			}
			seen[finding.object.kind+"/"+finding.object.namespace+"/"+finding.object.name] = true
		}
	}
	if !seen["Deployment/prod/web"] {
		t.Fatalf("the namespaced object did not survive the round trip; read back %v", seen)
	}
}