From 2ac78dbe558e75247ef4b1f35e510960cdd8d824 Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Fri, 8 Jul 2022 14:49:52 -0400 Subject: [PATCH 01/31] captures auditor name in the result --- auditors/apparmor/apparmor.go | 3 +++ auditors/asat/asat.go | 2 ++ auditors/capabilities/capabilities.go | 3 +++ auditors/deprecatedapis/depreceatedapis.go | 1 + auditors/deprecatedapis/depreceatedapis_test.go | 7 +++++++ auditors/hostns/hostns.go | 3 +++ auditors/image/image.go | 3 +++ auditors/limits/limits.go | 5 +++++ auditors/mounts/mounts.go | 1 + auditors/netpols/netpols.go | 8 ++++++++ auditors/nonroot/nonroot.go | 6 ++++++ auditors/privesc/privesc.go | 2 ++ auditors/privileged/privileged.go | 2 ++ auditors/rootfs/rootfs.go | 2 ++ auditors/seccomp/seccomp.go | 5 +++++ result.go | 1 + 16 files changed, 54 insertions(+) diff --git a/auditors/apparmor/apparmor.go b/auditors/apparmor/apparmor.go index c11c1379..0a7c26eb 100644 --- a/auditors/apparmor/apparmor.go +++ b/auditors/apparmor/apparmor.go @@ -64,6 +64,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isAppArmorAnnotationMissing(containerAnnotation, annotations) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: AppArmorAnnotationMissing, Severity: kubeaudit.Error, Message: fmt.Sprintf("AppArmor annotation missing. The annotation '%s' should be added.", containerAnnotation), @@ -80,6 +81,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isAppArmorDisabled(containerAnnotation, annotations) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: AppArmorDisabled, Message: fmt.Sprintf("AppArmor is disabled. The apparmor annotation should be set to '%s' or start with '%s'.", ProfileRuntimeDefault, ProfileNamePrefix), Severity: kubeaudit.Error, @@ -107,6 +109,7 @@ func auditPodAnnotations(resource k8s.Resource, containerNames []string) []*kube containerName := strings.Split(annotationKey, "/")[1] if !contains(containerNames, containerName) { auditResults = append(auditResults, &kubeaudit.AuditResult{ + Auditor: Name, Name: AppArmorInvalidAnnotation, Severity: kubeaudit.Error, Message: fmt.Sprintf("AppArmor annotation key refers to a container that doesn't exist. Remove the annotation '%s: %s'.", annotationKey, annotationValue), diff --git a/auditors/asat/asat.go b/auditors/asat/asat.go index c5fa3034..99639d1c 100644 --- a/auditors/asat/asat.go +++ b/auditors/asat/asat.go @@ -45,6 +45,7 @@ func auditResource(resource k8s.Resource, resources []k8s.Resource) *kubeaudit.A if isDeprecatedServiceAccountName(podSpec) && !hasServiceAccountName(podSpec) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: AutomountServiceAccountTokenDeprecated, Severity: kubeaudit.Warn, Message: "serviceAccount is a deprecated alias for serviceAccountName. serviceAccountName should be used instead.", @@ -60,6 +61,7 @@ func auditResource(resource k8s.Resource, resources []k8s.Resource) *kubeaudit.A defaultServiceAccount := getDefaultServiceAccount(resources) if usesDefaultServiceAccount(podSpec) && isAutomountTokenTrue(podSpec, defaultServiceAccount) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: AutomountServiceAccountTokenTrueAndDefaultSA, Severity: kubeaudit.Error, Message: "Default service account with token mounted. automountServiceAccountToken should be set to 'false' on either the ServiceAccount or on the PodSpec or a non-default service account should be used.", diff --git a/auditors/capabilities/capabilities.go b/auditors/capabilities/capabilities.go index 693dd69e..ce95ee71 100644 --- a/auditors/capabilities/capabilities.go +++ b/auditors/capabilities/capabilities.go @@ -75,6 +75,7 @@ func auditContainer(container *k8s.ContainerV1, capability string, allowAddList if IsCapabilityInAddList(container, capability) { message := fmt.Sprintf("Capability \"%s\" added. It should be removed from the capability add list. If you need this capability, add an override label such as '%s: SomeReason'.", capability, override.GetContainerOverrideLabel(container.Name, getOverrideLabel(capability))) auditResult := &kubeaudit.AuditResult{ + Auditor: Name, Name: CapabilityAdded, Severity: kubeaudit.Error, Message: message, @@ -103,6 +104,7 @@ func auditContainerForDropAll(container *k8s.ContainerV1) *kubeaudit.AuditResult if !SecurityContextOrCapabilities(container) { message := "Security Context not set. The Security Context should be specified and all Capabilities should be dropped by setting the Drop list to ALL." return &kubeaudit.AuditResult{ + Auditor: Name, Name: CapabilityOrSecurityContextMissing, Severity: kubeaudit.Error, Message: message, @@ -118,6 +120,7 @@ func auditContainerForDropAll(container *k8s.ContainerV1) *kubeaudit.AuditResult if !IsDropAll(container) { message := "Capability Drop list should be set to ALL. Add the specific ones you need to the Add list and set an override label." return &kubeaudit.AuditResult{ + Auditor: Name, Name: CapabilityShouldDropAll, Severity: kubeaudit.Error, Message: message, diff --git a/auditors/deprecatedapis/depreceatedapis.go b/auditors/deprecatedapis/depreceatedapis.go index eaad682c..f1bea0c4 100644 --- a/auditors/deprecatedapis/depreceatedapis.go +++ b/auditors/deprecatedapis/depreceatedapis.go @@ -119,6 +119,7 @@ func (deprecatedAPIs *DeprecatedAPIs) Audit(resource k8s.Resource, _ []k8s.Resou } } auditResult := &kubeaudit.AuditResult{ + Auditor: Name, Name: DeprecatedAPIUsed, Severity: severity, Message: deprecationMessage, diff --git a/auditors/deprecatedapis/depreceatedapis_test.go b/auditors/deprecatedapis/depreceatedapis_test.go index 749617f6..eb892e27 100644 --- a/auditors/deprecatedapis/depreceatedapis_test.go +++ b/auditors/deprecatedapis/depreceatedapis_test.go @@ -2,6 +2,7 @@ package deprecatedapis import ( "fmt" + "os" "strings" "testing" @@ -53,6 +54,12 @@ func TestAuditDeprecatedAPIs(t *testing.T) { require.Nil(t, err) report := test.AuditManifest(t, fixtureDir, tc.file, auditor, []string{DeprecatedAPIUsed}) assertReport(t, report, tc.expectedSeverity, message, metadata) + + // disable local tests when running in dev mode + if os.Getenv("USE_KIND") == "false" { + return + } + report = test.AuditLocal(t, fixtureDir, tc.file, auditor, fmt.Sprintf("%s-%d", strings.Split(tc.file, ".")[0], i), []string{DeprecatedAPIUsed}) assertReport(t, report, tc.expectedSeverity, message, metadata) }) diff --git a/auditors/hostns/hostns.go b/auditors/hostns/hostns.go index 8c897ae3..16264987 100644 --- a/auditors/hostns/hostns.go +++ b/auditors/hostns/hostns.go @@ -62,6 +62,7 @@ func auditHostNetwork(podSpec *k8s.PodSpecV1) *kubeaudit.AuditResult { metadata["PodHost"] = podSpec.Hostname } return &kubeaudit.AuditResult{ + Auditor: Name, Name: NamespaceHostNetworkTrue, Severity: kubeaudit.Error, Message: "hostNetwork is set to 'true' in PodSpec. It should be set to 'false'.", @@ -82,6 +83,7 @@ func auditHostIPC(podSpec *k8s.PodSpecV1) *kubeaudit.AuditResult { metadata["PodHost"] = podSpec.Hostname } return &kubeaudit.AuditResult{ + Auditor: Name, Name: NamespaceHostIPCTrue, Severity: kubeaudit.Error, Message: "hostIPC is set to 'true' in PodSpec. It should be set to 'false'.", @@ -102,6 +104,7 @@ func auditHostPID(podSpec *k8s.PodSpecV1) *kubeaudit.AuditResult { metadata["PodHost"] = podSpec.Hostname } return &kubeaudit.AuditResult{ + Auditor: Name, Name: NamespaceHostPIDTrue, Severity: kubeaudit.Error, Message: "hostPID is set to 'true' in PodSpec. It should be set to 'false'.", diff --git a/auditors/image/image.go b/auditors/image/image.go index 9be19f3a..1678481e 100644 --- a/auditors/image/image.go +++ b/auditors/image/image.go @@ -50,6 +50,7 @@ func auditContainer(container *k8s.ContainerV1, image string) *kubeaudit.AuditRe if isImageTagMissing(containerTag) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: ImageTagMissing, Severity: kubeaudit.Warn, Message: "Image tag is missing.", @@ -61,6 +62,7 @@ func auditContainer(container *k8s.ContainerV1, image string) *kubeaudit.AuditRe if isImageTagIncorrect(name, tag, containerName, containerTag) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: ImageTagIncorrect, Severity: kubeaudit.Error, Message: fmt.Sprintf("Container tag is incorrect. It should be set to '%s'.", tag), @@ -72,6 +74,7 @@ func auditContainer(container *k8s.ContainerV1, image string) *kubeaudit.AuditRe if isImageCorrect(name, tag, containerName, containerTag) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: ImageCorrect, Severity: kubeaudit.Info, Message: "Image tag is correct", diff --git a/auditors/limits/limits.go b/auditors/limits/limits.go index 833457a0..0b7988fa 100644 --- a/auditors/limits/limits.go +++ b/auditors/limits/limits.go @@ -65,6 +65,7 @@ func (limits *Limits) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaud func (limits *Limits) auditContainer(container *k8s.ContainerV1) (auditResults []*kubeaudit.AuditResult) { if isLimitsNil(container) { auditResult := &kubeaudit.AuditResult{ + Auditor: Name, Name: LimitsNotSet, Severity: kubeaudit.Warn, Message: "Resource limits not set.", @@ -81,6 +82,7 @@ func (limits *Limits) auditContainer(container *k8s.ContainerV1) (auditResults [ if isCPULimitUnset(container) { auditResult := &kubeaudit.AuditResult{ + Auditor: Name, Name: LimitsCPUNotSet, Severity: kubeaudit.Warn, Message: "Resource CPU limit not set.", @@ -92,6 +94,7 @@ func (limits *Limits) auditContainer(container *k8s.ContainerV1) (auditResults [ } else if exceedsCPULimit(container, limits) { maxCPU := limits.maxCPU.String() auditResult := &kubeaudit.AuditResult{ + Auditor: Name, Name: LimitsCPUExceeded, Severity: kubeaudit.Warn, Message: fmt.Sprintf("CPU limit exceeded. It is set to '%s' which exceeds the max CPU limit of '%s'.", cpu, maxCPU), @@ -106,6 +109,7 @@ func (limits *Limits) auditContainer(container *k8s.ContainerV1) (auditResults [ if isMemoryLimitUnset(container) { auditResult := &kubeaudit.AuditResult{ + Auditor: Name, Name: LimitsMemoryNotSet, Severity: kubeaudit.Warn, Message: "Resource Memory limit not set.", @@ -117,6 +121,7 @@ func (limits *Limits) auditContainer(container *k8s.ContainerV1) (auditResults [ } else if exceedsMemoryLimit(container, limits) { maxMemory := limits.maxMemory.String() auditResult := &kubeaudit.AuditResult{ + Auditor: Name, Name: LimitsMemoryExceeded, Severity: kubeaudit.Warn, Message: fmt.Sprintf("Memory limit exceeded. It is set to '%s' which exceeds the max Memory limit of '%s'.", memory, maxMemory), diff --git a/auditors/mounts/mounts.go b/auditors/mounts/mounts.go index d6ea3107..efac26e0 100644 --- a/auditors/mounts/mounts.go +++ b/auditors/mounts/mounts.go @@ -100,6 +100,7 @@ func auditContainer(container *k8s.ContainerV1, sensitiveVolumes map[string]v1.V for _, mount := range container.VolumeMounts { if volume, ok := sensitiveVolumes[mount.Name]; ok { auditResults = append(auditResults, &kubeaudit.AuditResult{ + Auditor: Name, Name: SensitivePathsMounted, Severity: kubeaudit.Error, Message: fmt.Sprintf("Sensitive path mounted as volume: %s (hostPath: %s). It should be removed from the container's mounts list.", mount.Name, volume.HostPath.Path), diff --git a/auditors/netpols/netpols.go b/auditors/netpols/netpols.go index e2c1f88a..9c796709 100644 --- a/auditors/netpols/netpols.go +++ b/auditors/netpols/netpols.go @@ -72,6 +72,7 @@ func auditNetworkPolicy(networkPolicy *k8s.NetworkPolicyV1) []*kubeaudit.AuditRe if allIngressTrafficAllowed(networkPolicy) { auditResult := &kubeaudit.AuditResult{ + Auditor: Name, Name: AllowAllIngressNetworkPolicyExists, Severity: kubeaudit.Warn, Message: "Found allow all ingress traffic NetworkPolicy.", @@ -84,6 +85,7 @@ func auditNetworkPolicy(networkPolicy *k8s.NetworkPolicyV1) []*kubeaudit.AuditRe if allEgressTrafficAllowed(networkPolicy) { auditResult := &kubeaudit.AuditResult{ + Auditor: Name, Name: AllowAllEgressNetworkPolicyExists, Severity: kubeaudit.Warn, Message: "Found allow all egress traffic NetworkPolicy.", @@ -108,6 +110,7 @@ func auditNetworkPoliciesForDenyAll(resource k8s.Resource, resources []k8s.Resou if hasCatchAllNetPol { if !hasDefaultDenyIngress { auditResult := &kubeaudit.AuditResult{ + Auditor: Name, Name: MissingDefaultDenyIngressNetworkPolicy, Severity: kubeaudit.Error, Message: fmt.Sprintf("All ingress traffic should be blocked by default for namespace %s.", namespace), @@ -125,6 +128,7 @@ func auditNetworkPoliciesForDenyAll(resource k8s.Resource, resources []k8s.Resou if !hasDefaultDenyEgress { auditResult := &kubeaudit.AuditResult{ + Auditor: Name, Name: MissingDefaultDenyEgressNetworkPolicy, Severity: kubeaudit.Error, Message: fmt.Sprintf("All egress traffic should be blocked by default for namespace %s.", namespace), @@ -149,6 +153,7 @@ func auditNetworkPoliciesForDenyAll(resource k8s.Resource, resources []k8s.Resou if !hasIngressOverride && !hasEgressOverride { auditResult := &kubeaudit.AuditResult{ + Auditor: Name, Name: MissingDefaultDenyIngressAndEgressNetworkPolicy, Severity: kubeaudit.Error, Message: "Namespace is missing a default deny ingress and egress NetworkPolicy.", @@ -165,6 +170,7 @@ func auditNetworkPoliciesForDenyAll(resource k8s.Resource, resources []k8s.Resou if hasIngressOverride && hasEgressOverride { auditResult := &kubeaudit.AuditResult{ + Auditor: Name, Name: override.GetOverriddenResultName(MissingDefaultDenyIngressAndEgressNetworkPolicy), Severity: kubeaudit.Warn, Message: "Namespace is missing a default deny ingress and egress NetworkPolicy.", @@ -179,6 +185,7 @@ func auditNetworkPoliciesForDenyAll(resource k8s.Resource, resources []k8s.Resou // At this point there is exactly one override label for either ingress or egress which means one needs to be // fixed and the other is overridden auditResult := &kubeaudit.AuditResult{ + Auditor: Name, Name: MissingDefaultDenyIngressNetworkPolicy, Severity: kubeaudit.Error, Message: "Namespace is missing a default deny ingress NetworkPolicy.", @@ -194,6 +201,7 @@ func auditNetworkPoliciesForDenyAll(resource k8s.Resource, resources []k8s.Resou auditResults = append(auditResults, auditResult) auditResult = &kubeaudit.AuditResult{ + Auditor: Name, Name: MissingDefaultDenyEgressNetworkPolicy, Severity: kubeaudit.Error, Message: "Namespace is missing a default deny egress NetworkPolicy.", diff --git a/auditors/nonroot/nonroot.go b/auditors/nonroot/nonroot.go index 8a09ca2d..83e0d9ed 100644 --- a/auditors/nonroot/nonroot.go +++ b/auditors/nonroot/nonroot.go @@ -56,6 +56,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if !isContainerRunAsUserNil(container) { if *container.SecurityContext.RunAsUser == 0 { return &kubeaudit.AuditResult{ + Auditor: Name, Name: RunAsUserCSCRoot, Severity: kubeaudit.Error, Message: "runAsUser is set to UID 0 (root user) in the container SecurityContext. Either set it to a value > 0 or remove it and set runAsNonRoot to true.", @@ -71,6 +72,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if !isPodRunAsUserNil(podSpec) { if *podSpec.SecurityContext.RunAsUser == 0 { return &kubeaudit.AuditResult{ + Auditor: Name, Name: RunAsUserPSCRoot, Severity: kubeaudit.Warn, Message: "runAsUser is set to UID 0 (root user) in the PodSecurityContext. Either set it to a value > 0 or remove it and set runAsNonRoot to true.", @@ -87,6 +89,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if !isPodRunAsUserNil(podSpec) { if *podSpec.SecurityContext.RunAsUser == 0 { return &kubeaudit.AuditResult{ + Auditor: Name, Name: RunAsUserPSCRoot, Severity: kubeaudit.Error, Message: "runAsUser is set to UID 0 (root user) in the PodSecurityContext. Either set it to a value > 0 or remove it and set runAsNonRoot to true.", @@ -104,6 +107,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isContainerRunAsNonRootCSCFalse(container) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: RunAsNonRootCSCFalse, Severity: kubeaudit.Error, Message: "runAsNonRoot is set to false in the container SecurityContext. Either set it to true or set runAsUser to a value > 0.", @@ -119,6 +123,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isContainerRunAsNonRootNil(container) { if isPodRunAsNonRootNil(podSpec) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: RunAsNonRootPSCNilCSCNil, Severity: kubeaudit.Error, Message: "runAsNonRoot should be set to true or runAsUser should be set to a value > 0 either in the container SecurityContext or PodSecurityContext.", @@ -133,6 +138,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isPodRunAsNonRootFalse(podSpec) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: RunAsNonRootPSCFalseCSCNil, Severity: kubeaudit.Error, Message: "runAsNonRoot is set to false in the PodSecurityContext. Either set it to true or set runAsUser to a value > 0.", diff --git a/auditors/privesc/privesc.go b/auditors/privesc/privesc.go index 924e8d59..531d236d 100644 --- a/auditors/privesc/privesc.go +++ b/auditors/privesc/privesc.go @@ -44,6 +44,7 @@ func (a *AllowPrivilegeEscalation) Audit(resource k8s.Resource, _ []k8s.Resource func auditContainer(container *k8s.ContainerV1) *kubeaudit.AuditResult { if isAllowPrivilegeEscalationNil(container) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: AllowPrivilegeEscalationNil, Severity: kubeaudit.Error, Message: "allowPrivilegeEscalation not set which allows privilege escalation. It should be set to 'false'.", @@ -58,6 +59,7 @@ func auditContainer(container *k8s.ContainerV1) *kubeaudit.AuditResult { if isAllowPrivilegeEscalationTrue(container) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: AllowPrivilegeEscalationTrue, Severity: kubeaudit.Error, Message: "allowPrivilegeEscalation set to 'true'. It should be set to 'false'.", diff --git a/auditors/privileged/privileged.go b/auditors/privileged/privileged.go index 0fa9ee50..f3b23efa 100644 --- a/auditors/privileged/privileged.go +++ b/auditors/privileged/privileged.go @@ -43,6 +43,7 @@ func (a *Privileged) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudi func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudit.AuditResult { if isPrivilegedNil(container) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: PrivilegedNil, Severity: kubeaudit.Warn, Message: "privileged is not set in container SecurityContext. Privileged defaults to 'false' but it should be explicitly set to 'false'.", @@ -57,6 +58,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isPrivilegedTrue(container) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: PrivilegedTrue, Severity: kubeaudit.Error, Message: "privileged is set to 'true' in container SecurityContext. It should be set to 'false'.", diff --git a/auditors/rootfs/rootfs.go b/auditors/rootfs/rootfs.go index 044590a4..319fecc9 100644 --- a/auditors/rootfs/rootfs.go +++ b/auditors/rootfs/rootfs.go @@ -43,6 +43,7 @@ func (a *ReadOnlyRootFilesystem) Audit(resource k8s.Resource, _ []k8s.Resource) func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudit.AuditResult { if isReadOnlyRootFilesystemNil(container) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: ReadOnlyRootFilesystemNil, Severity: kubeaudit.Error, Message: "readOnlyRootFilesystem is not set in container SecurityContext. It should be set to 'true'.", @@ -57,6 +58,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isReadOnlyRootFilesystemFalse(container) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: ReadOnlyRootFilesystemFalse, Severity: kubeaudit.Error, Message: "readOnlyRootFilesystem is set to 'false' in container SecurityContext. It should be set to 'true'.", diff --git a/auditors/seccomp/seccomp.go b/auditors/seccomp/seccomp.go index 0f612207..91ab760d 100644 --- a/auditors/seccomp/seccomp.go +++ b/auditors/seccomp/seccomp.go @@ -76,6 +76,7 @@ func auditPod(resource k8s.Resource) *kubeaudit.AuditResult { } return &kubeaudit.AuditResult{ + Auditor: Name, Name: SeccompAnnotationMissing, Severity: kubeaudit.Error, Message: fmt.Sprintf("Seccomp annotation is missing. The annotation %s: %s should be added.", PodAnnotationKey, ProfileRuntimeDefault), @@ -93,6 +94,7 @@ func auditPod(resource k8s.Resource) *kubeaudit.AuditResult { if isSeccompProfileDeprecated(podSeccompProfile) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: SeccompDeprecatedPod, Severity: kubeaudit.Error, Message: fmt.Sprintf("Seccomp pod annotation is set to deprecated value %s. It should be set to %s instead.", podSeccompProfile, ProfileRuntimeDefault), @@ -109,6 +111,7 @@ func auditPod(resource k8s.Resource) *kubeaudit.AuditResult { if !isSeccompEnabled(podSeccompProfile) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: SeccompDisabledPod, Severity: kubeaudit.Error, Message: fmt.Sprintf("Seccomp pod annotation is set to %s which disables Seccomp. It should be set to the default profile %s or should start with %s.", podSeccompProfile, ProfileRuntimeDefault, ProfileNamePrefix), @@ -154,6 +157,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isSeccompProfileDeprecated(containerSeccompProfile) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: SeccompDeprecatedContainer, Severity: kubeaudit.Error, Message: fmt.Sprintf("Seccomp container annotation is set to deprecated value %s. It should be set to %s instead.", containerSeccompProfile, ProfileRuntimeDefault), @@ -167,6 +171,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if !isSeccompEnabled(containerSeccompProfile) { return &kubeaudit.AuditResult{ + Auditor: Name, Name: SeccompDisabledContainer, Severity: kubeaudit.Error, Message: fmt.Sprintf("Seccomp container annotation is set to %s which disables Seccomp. It should be set to the default profile %s or should start with %s.", containerSeccompProfile, ProfileRuntimeDefault, ProfileNamePrefix), diff --git a/result.go b/result.go index fda3339c..e4f8ddf3 100644 --- a/result.go +++ b/result.go @@ -37,6 +37,7 @@ func (s SeverityLevel) String() string { // AuditResult represents a potential security issue. There may be multiple AuditResults per resource and audit type AuditResult struct { + Auditor string // auditor name Name string // Name uniquely identifies a type of audit result Severity SeverityLevel // Severity is one of Error, Warn, or Info Message string // Message is a human-readable description of the audit result From 809dacd699ed6d7aa6dff3aa73291504fd47fec2 Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Fri, 8 Jul 2022 15:24:17 -0400 Subject: [PATCH 02/31] adds file path to audit result - for sarif location --- cmd/commands/root.go | 2 +- example_custom_test.go | 2 +- example_test.go | 6 +++--- internal/test/test.go | 4 ++-- kubeaudit.go | 9 ++++++++- result.go | 1 + 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/cmd/commands/root.go b/cmd/commands/root.go index 631f00d9..91fbc120 100644 --- a/cmd/commands/root.go +++ b/cmd/commands/root.go @@ -108,7 +108,7 @@ func getReport(auditors ...kubeaudit.Auditable) *kubeaudit.Report { f = manifest } - report, err := auditor.AuditManifest(f) + report, err := auditor.AuditManifest(rootConfig.manifest, f) if err != nil { log.WithError(err).Fatal("Error auditing manifest") } diff --git a/example_custom_test.go b/example_custom_test.go index b4d50deb..44e7cefd 100644 --- a/example_custom_test.go +++ b/example_custom_test.go @@ -98,7 +98,7 @@ func Example_customAuditor() { } // Run the audit in the mode of your choosing. Here we use manifest mode. - report, err := auditor.AuditManifest(strings.NewReader(manifest)) + report, err := auditor.AuditManifest("", strings.NewReader(manifest)) if err != nil { log.Fatal(err) } diff --git a/example_test.go b/example_test.go index 41c51e04..79cfbf59 100644 --- a/example_test.go +++ b/example_test.go @@ -43,7 +43,7 @@ metadata: } // Run the audit in manifest mode - report, err := auditor.AuditManifest(strings.NewReader(manifest)) + report, err := auditor.AuditManifest("", strings.NewReader(manifest)) if err != nil { log.Fatal(err) } @@ -136,7 +136,7 @@ metadata: } // Run the audit in the mode of your choosing. Here we use manifest mode. - report, err := auditor.AuditManifest(strings.NewReader(manifest)) + report, err := auditor.AuditManifest("", strings.NewReader(manifest)) if err != nil { log.Fatal(err) } @@ -188,7 +188,7 @@ metadata: } // Run the audit in the mode of your choosing. Here we use manifest mode. - report, err := auditor.AuditManifest(strings.NewReader(manifest)) + report, err := auditor.AuditManifest("", strings.NewReader(manifest)) if err != nil { log.Fatal(err) } diff --git a/internal/test/test.go b/internal/test/test.go index ead07904..ecf86f6a 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -79,7 +79,7 @@ func FixSetupMultiple(t *testing.T, fixtureDir, fixture string, auditables []kub auditor, err := kubeaudit.New(auditables) require.Nil(err) - report, err = auditor.AuditManifest(fixedManifest) + report, err = auditor.AuditManifest("", fixedManifest) require.Nil(err) results := report.RawResults() @@ -107,7 +107,7 @@ func GetReport(t *testing.T, fixtureDir, fixture string, auditables []kubeaudit. case MANIFEST_MODE: manifest, openErr := os.Open(fixture) require.NoError(openErr) - report, err = auditor.AuditManifest(manifest) + report, err = auditor.AuditManifest("", manifest) case LOCAL_MODE: defer DeleteNamespace(t, namespace) CreateNamespace(t, namespace) diff --git a/kubeaudit.go b/kubeaudit.go index aea3852d..b1fd5d0b 100644 --- a/kubeaudit.go +++ b/kubeaudit.go @@ -138,7 +138,7 @@ func New(auditors []Auditable, opts ...Option) (*Kubeaudit, error) { } // AuditManifest audits the Kubernetes resources in the provided manifest -func (a *Kubeaudit) AuditManifest(manifest io.Reader) (*Report, error) { +func (a *Kubeaudit) AuditManifest(manifestPath string, manifest io.Reader) (*Report, error) { manifestBytes, err := ioutil.ReadAll(manifest) if err != nil { return nil, err @@ -154,6 +154,13 @@ func (a *Kubeaudit) AuditManifest(manifest io.Reader) (*Report, error) { return nil, err } + for _, result := range results { + auditResults := result.GetAuditResults() + for _, ar := range auditResults { + ar.FilePath = manifestPath + } + } + report := &Report{results: results} return report, nil diff --git a/result.go b/result.go index e4f8ddf3..e769b3c1 100644 --- a/result.go +++ b/result.go @@ -43,6 +43,7 @@ type AuditResult struct { Message string // Message is a human-readable description of the audit result PendingFix PendingFix // PendingFix is the fix that will be applied to automatically fix the security issue Metadata Metadata // Metadata includes additional context for an audit result + FilePath string // manifest file path } func (result *AuditResult) Fix(resource k8s.Resource) (newResources []k8s.Resource) { From 184bfc7de6c009873f21430068ce4cdb0b3a6bba Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Fri, 8 Jul 2022 15:30:24 -0400 Subject: [PATCH 03/31] adds sarif internal pkg --- go.mod | 1 + go.sum | 8 ++ .../sarif/fixtures/apparmor-disabled.yaml | 11 ++ .../sarif/fixtures/capabilities-added.yaml | 26 ++++ internal/sarif/sarif.go | 115 ++++++++++++++++++ internal/sarif/sarif_test.go | 69 +++++++++++ 6 files changed, 230 insertions(+) create mode 100644 internal/sarif/fixtures/apparmor-disabled.yaml create mode 100644 internal/sarif/fixtures/capabilities-added.yaml create mode 100644 internal/sarif/sarif.go create mode 100644 internal/sarif/sarif_test.go diff --git a/go.mod b/go.mod index be981126..de5d4a44 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( k8s.io/apiextensions-apiserver v0.23.5 k8s.io/apimachinery v0.24.3 k8s.io/client-go v0.24.3 + github.com/owenrumney/go-sarif/v2 v2.1.2 ) require ( diff --git a/go.sum b/go.sum index a4619bf7..1adbee04 100644 --- a/go.sum +++ b/go.sum @@ -144,6 +144,7 @@ github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:C github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -889,6 +890,10 @@ github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3 github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= github.com/opencontainers/selinux v1.9.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +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/owenrumney/go-sarif/v2 v2.1.2 h1:PMDK7tXShJ9zsB7bfvlpADH5NEw1dfA9xwU8Xtdj73U= +github.com/owenrumney/go-sarif/v2 v2.1.2/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= @@ -1059,6 +1064,8 @@ github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:tw github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -1077,6 +1084,7 @@ github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= diff --git a/internal/sarif/fixtures/apparmor-disabled.yaml b/internal/sarif/fixtures/apparmor-disabled.yaml new file mode 100644 index 00000000..b2593ab1 --- /dev/null +++ b/internal/sarif/fixtures/apparmor-disabled.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod + namespace: apparmor-disabled + annotations: + container.apparmor.security.beta.kubernetes.io/container: badval +spec: + containers: + - name: container + image: scratch diff --git a/internal/sarif/fixtures/capabilities-added.yaml b/internal/sarif/fixtures/capabilities-added.yaml new file mode 100644 index 00000000..f5138302 --- /dev/null +++ b/internal/sarif/fixtures/capabilities-added.yaml @@ -0,0 +1,26 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment + namespace: capabilities-added + annotations: + container.apparmor.security.beta.kubernetes.io/container: badval +spec: + selector: + matchLabels: + name: deployment + template: + metadata: + labels: + name: deployment + spec: + containers: + - name: container + image: scratch + securityContext: + capabilities: + add: + - NET_ADMIN + - CHOWN + drop: + - ALL diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go new file mode 100644 index 00000000..14abd79b --- /dev/null +++ b/internal/sarif/sarif.go @@ -0,0 +1,115 @@ +package sarif + +import ( + "fmt" + "strings" + + "github.com/Shopify/kubeaudit" + "github.com/Shopify/kubeaudit/auditors/apparmor" + "github.com/Shopify/kubeaudit/auditors/asat" + "github.com/Shopify/kubeaudit/auditors/capabilities" + "github.com/Shopify/kubeaudit/auditors/hostns" + "github.com/Shopify/kubeaudit/auditors/image" + "github.com/Shopify/kubeaudit/auditors/limits" + "github.com/Shopify/kubeaudit/auditors/mounts" + "github.com/Shopify/kubeaudit/auditors/netpols" + "github.com/Shopify/kubeaudit/auditors/nonroot" + "github.com/Shopify/kubeaudit/auditors/privesc" + "github.com/Shopify/kubeaudit/auditors/privileged" + "github.com/Shopify/kubeaudit/auditors/rootfs" + "github.com/Shopify/kubeaudit/auditors/seccomp" + "github.com/owenrumney/go-sarif/v2/sarif" +) + +var AuditorNames = map[string]string{ + apparmor.Name: "Finds containers that do not have AppArmor enabled", + asat.Name: "Finds containers where the deprecated SA field is used or with a mounted default SA", + capabilities.Name: "Finds containers that do not drop the recommended capabilities or add new ones", + hostns.Name: "Finds containers that have HostPID, HostIPC or HostNetwork enabled", + image.Name: "Finds containers which do not use the desired version of an image (via the tag) or use an image without a tag", + limits.Name: "Finds containers which exceed the specified CPU and memory limits or do not specify any", + mounts.Name: "Finds containers that have sensitive host paths mounted", + netpols.Name: "Finds namespaces that do not have a default-deny network policy", + nonroot.Name: "Finds containers allowed to run as root", + privesc.Name: "Finds containers that allow privilege escalation", + privileged.Name: "Finds containers running as privileged", + rootfs.Name: "Finds containers which do not have a read-only filesystem", + seccomp.Name: "Finds containers running without seccomp", +} + +func CreateSarifReport() (*sarif.Report, *sarif.Run) { + // create a new report object + report, err := sarif.New(sarif.Version210) + if err != nil { + panic(err) + } + + // create a run for kubeaudit + run := sarif.NewRunWithInformationURI("kubeaudit", "https://github.com/Shopify/kubeaudit") + + report.AddRun(run) + + return report, run +} + +func AddSarifRules(kubeauditReport *kubeaudit.Report, run *sarif.Run) { + var results []*kubeaudit.AuditResult + + for _, reportResult := range kubeauditReport.Results() { + r := reportResult.GetAuditResults() + results = append(results, r...) + } + + for _, result := range results { + auditor := strings.ToLower(result.Auditor) + ruleID := strings.ToLower(result.Name) + var docsURL string + // create a new rule for each rule id + if strings.Contains(ruleID, auditor) { + docsURL = "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/" + auditor + ".md" + } + + helpMessage := fmt.Sprintf("**Type**: kubernetes\n**Docs**: %s\n**Description:** %s", docsURL, AuditorNames[auditor]) + + run.AddRule(result.Name). + WithName(result.Auditor). + WithMarkdownHelp(helpMessage). + WithProperties(sarif.Properties{ + "tags": []string{ + "security", + "kubernetes", + "infrastructure", + }, + "precision": "very-high", + }) + } +} + +func AddSarifResult(kubeauditReport *kubeaudit.Report, run *sarif.Run) { + var results []*kubeaudit.AuditResult + + for _, reportResult := range kubeauditReport.Results() { + r := reportResult.GetAuditResults() + results = append(results, r...) + } + + for _, r := range results { + severityLevel := r.Severity.String() + + // SARIF specifies the following severity levels: warning, error, note and none + // https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html + // so we're converting info to none here so we get valid SARIF output + if r.Severity.String() == "info" { + severityLevel = "note" + } + + location := sarif.NewPhysicalLocation(). + WithArtifactLocation(sarif.NewSimpleArtifactLocation(r.FilePath).WithUriBaseId("ROOTPATH")). + WithRegion(sarif.NewRegion().WithStartLine(1)) + result := sarif.NewRuleResult(r.Name). + WithMessage(sarif.NewTextMessage(r.Message)). + WithLevel(severityLevel). + WithLocations([]*sarif.Location{sarif.NewLocation().WithPhysicalLocation(location)}) + run.AddResult(result) + } +} diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go new file mode 100644 index 00000000..c22d41c0 --- /dev/null +++ b/internal/sarif/sarif_test.go @@ -0,0 +1,69 @@ +package sarif + +import ( + "os" + "path/filepath" + "testing" + + "github.com/Shopify/kubeaudit" + "github.com/Shopify/kubeaudit/auditors/apparmor" + "github.com/Shopify/kubeaudit/auditors/capabilities" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCreateSarifReport(t *testing.T) { + sarifReport, _ := CreateSarifReport() + require.Len(t, sarifReport.Runs, 1) + assert.Equal(t, "https://github.com/Shopify/kubeaudit", *sarifReport.Runs[0].Tool.Driver.InformationURI) +} + +func TestAddSarifResultToReport(t *testing.T) { + capabilitiesAuditable := capabilities.New(capabilities.Config{}) + apparmorAuditable := apparmor.New() + + auditables := []kubeaudit.Auditable{capabilitiesAuditable, apparmorAuditable} + + cases := []struct { + file string + auditor string + }{ + {"apparmor-disabled.yaml", apparmor.Name}, + {"capabilities-added.yaml", capabilities.Name}, + } + + for _, tc := range cases { + fixture := filepath.Join("fixtures", tc.file) + auditor, err := kubeaudit.New(auditables) + require.NoError(t, err) + + manifest, openErr := os.Open(fixture) + require.NoError(t, openErr) + + kubeAuditReport, err := auditor.AuditManifest(fixture, manifest) + require.NoError(t, err) + + // we're only appending sarif to the path here for testing purposes + for _, reportResult := range kubeAuditReport.Results() { + r := reportResult.GetAuditResults() + + for _, auditResult := range r { + auditResult.FilePath = filepath.Join("sarif/", auditResult.FilePath) + } + } + + // verify that the file path is correct + assert.Contains(t, kubeAuditReport.Results()[0].GetAuditResults()[0].FilePath, "sarif/fixtures") + + sarifReport, sarifRun := CreateSarifReport() + + AddSarifRules(kubeAuditReport, sarifRun) + + // verify that the 2 rules (auditors) enabled have been added to the report + require.Len(t, *&sarifReport.Runs[0].Tool.Driver.Rules, 2) + + AddSarifResult(kubeAuditReport, sarifRun) + } +} + +// todo: verify the contents of the report From c17b5ec1b9938aad8323d276d268de0a68247e1e Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Fri, 8 Jul 2022 15:35:06 -0400 Subject: [PATCH 04/31] supports sarif flag to output the desired format --- README.md | 3 +++ cmd/commands/root.go | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/README.md b/README.md index f5ee9fd4..9c688cfa 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,8 @@ The minimum severity level can be set using the `--minSeverity/-m` flag. By default kubeaudit will output results in a human-readable way. If the output is intended to be further processed, it can be set to output JSON using the `--format json` flag. To output results as logs (the previous default) use `--format logrus`. Some output formats include colors to make results easier to read in a terminal. To disable colors (for example, if you are sending output to a text file), you can use the `--no-color` flag. +You can generate a kubeaudit report in [SARIF](https://docs.oasis-open.org/sarif/sarif/v2.0/sarif-v2.0.html) and write it to a file by using the `-s/--sarif` flag. + If there are results of severity level `error`, kubeaudit will exit with exit code 2. This can be changed using the `--exitcode/-e` flag. For all the ways kubeaudit can be customized, see [Global Flags](#global-flags). @@ -221,6 +223,7 @@ Auditors can also be run individually. | -m | --minseverity | Set the lowest severity level to report (one of "error", "warning", "info") (default is "info") | | -e | --exitcode | Exit code to use if there are results with severity of "error". Conventionally, 0 is used for success and all non-zero codes for an error. (default is 2) | | | --no-color | Don't use colors in the output (default is false) | +| -s | --sarif | The file location to save the SARIF output | ## Configuration File diff --git a/cmd/commands/root.go b/cmd/commands/root.go index 91fbc120..29140c84 100644 --- a/cmd/commands/root.go +++ b/cmd/commands/root.go @@ -12,12 +12,14 @@ import ( "github.com/Shopify/kubeaudit/auditors/all" "github.com/Shopify/kubeaudit/config" "github.com/Shopify/kubeaudit/internal/k8sinternal" + "github.com/Shopify/kubeaudit/internal/sarif" ) var rootConfig rootFlags type rootFlags struct { format string + sarifOut string kubeConfig string context string manifest string @@ -53,6 +55,7 @@ func init() { RootCmd.PersistentFlags().StringVarP(&rootConfig.context, "context", "c", "", "The name of the kubeconfig context to use") RootCmd.PersistentFlags().StringVarP(&rootConfig.minSeverity, "minseverity", "m", "info", "Set the lowest severity level to report (one of \"error\", \"warning\", \"info\")") RootCmd.PersistentFlags().StringVarP(&rootConfig.format, "format", "p", "pretty", "The output format to use (one of \"pretty\", \"logrus\", \"json\")") + RootCmd.PersistentFlags().StringVarP(&rootConfig.sarifOut, "sarif", "s", "", "The path to output sarif report to") RootCmd.PersistentFlags().StringVarP(&rootConfig.namespace, "namespace", "n", apiv1.NamespaceAll, "Only audit resources in the specified namespace. Not currently supported in manifest mode.") RootCmd.PersistentFlags().BoolVarP(&rootConfig.includeGenerated, "includegenerated", "g", false, "Include generated resources in scan (eg. pods generated by deployments).") RootCmd.PersistentFlags().BoolVar(&rootConfig.noColor, "no-color", false, "Don't produce colored output.") @@ -77,6 +80,13 @@ func runAudit(auditable ...kubeaudit.Auditable) func(cmd *cobra.Command, args [] kubeaudit.WithColor(!rootConfig.noColor), } + if rootConfig.sarifOut != "" { + sarifReport, sarifRun := sarif.CreateSarifReport() + sarif.AddSarifRules(report, sarifRun) + sarif.AddSarifResult(report, sarifRun) + sarifReport.WriteFile(rootConfig.sarifOut) + } + switch rootConfig.format { case "json": printOptions = append(printOptions, kubeaudit.WithFormatter(&log.JSONFormatter{})) From f245a9a6faa2458c091cd96ccde891ad9324d4a8 Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Mon, 11 Jul 2022 10:28:38 -0400 Subject: [PATCH 05/31] renames result field --- auditors/apparmor/apparmor.go | 6 +++--- auditors/asat/asat.go | 4 ++-- auditors/capabilities/capabilities.go | 6 +++--- auditors/deprecatedapis/depreceatedapis.go | 2 +- auditors/hostns/hostns.go | 6 +++--- auditors/image/image.go | 6 +++--- auditors/limits/limits.go | 10 +++++----- auditors/mounts/mounts.go | 2 +- auditors/netpols/netpols.go | 16 ++++++++-------- auditors/nonroot/nonroot.go | 12 ++++++------ auditors/privesc/privesc.go | 4 ++-- auditors/privileged/privileged.go | 4 ++-- auditors/rootfs/rootfs.go | 4 ++-- auditors/seccomp/seccomp.go | 10 +++++----- cmd/commands/root.go | 5 ++++- example_custom_test.go | 2 +- internal/sarif/sarif.go | 15 ++++++++------- internal/sarif/sarif_test.go | 7 +++++-- internal/test/test.go | 2 +- pkg/override/override.go | 4 ++-- printer.go | 4 ++-- result.go | 2 +- util_test.go | 2 +- 23 files changed, 71 insertions(+), 64 deletions(-) diff --git a/auditors/apparmor/apparmor.go b/auditors/apparmor/apparmor.go index 0a7c26eb..24e820d8 100644 --- a/auditors/apparmor/apparmor.go +++ b/auditors/apparmor/apparmor.go @@ -65,7 +65,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isAppArmorAnnotationMissing(containerAnnotation, annotations) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: AppArmorAnnotationMissing, + Rule: AppArmorAnnotationMissing, Severity: kubeaudit.Error, Message: fmt.Sprintf("AppArmor annotation missing. The annotation '%s' should be added.", containerAnnotation), Metadata: kubeaudit.Metadata{ @@ -82,7 +82,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isAppArmorDisabled(containerAnnotation, annotations) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: AppArmorDisabled, + Rule: AppArmorDisabled, Message: fmt.Sprintf("AppArmor is disabled. The apparmor annotation should be set to '%s' or start with '%s'.", ProfileRuntimeDefault, ProfileNamePrefix), Severity: kubeaudit.Error, Metadata: kubeaudit.Metadata{ @@ -110,7 +110,7 @@ func auditPodAnnotations(resource k8s.Resource, containerNames []string) []*kube if !contains(containerNames, containerName) { auditResults = append(auditResults, &kubeaudit.AuditResult{ Auditor: Name, - Name: AppArmorInvalidAnnotation, + Rule: AppArmorInvalidAnnotation, Severity: kubeaudit.Error, Message: fmt.Sprintf("AppArmor annotation key refers to a container that doesn't exist. Remove the annotation '%s: %s'.", annotationKey, annotationValue), Metadata: kubeaudit.Metadata{ diff --git a/auditors/asat/asat.go b/auditors/asat/asat.go index 99639d1c..b20dc7da 100644 --- a/auditors/asat/asat.go +++ b/auditors/asat/asat.go @@ -46,7 +46,7 @@ func auditResource(resource k8s.Resource, resources []k8s.Resource) *kubeaudit.A if isDeprecatedServiceAccountName(podSpec) && !hasServiceAccountName(podSpec) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: AutomountServiceAccountTokenDeprecated, + Rule: AutomountServiceAccountTokenDeprecated, Severity: kubeaudit.Warn, Message: "serviceAccount is a deprecated alias for serviceAccountName. serviceAccountName should be used instead.", PendingFix: &fixDeprecatedServiceAccountName{ @@ -62,7 +62,7 @@ func auditResource(resource k8s.Resource, resources []k8s.Resource) *kubeaudit.A if usesDefaultServiceAccount(podSpec) && isAutomountTokenTrue(podSpec, defaultServiceAccount) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: AutomountServiceAccountTokenTrueAndDefaultSA, + Rule: AutomountServiceAccountTokenTrueAndDefaultSA, Severity: kubeaudit.Error, Message: "Default service account with token mounted. automountServiceAccountToken should be set to 'false' on either the ServiceAccount or on the PodSpec or a non-default service account should be used.", PendingFix: &fixDefaultServiceAccountWithAutomountToken{ diff --git a/auditors/capabilities/capabilities.go b/auditors/capabilities/capabilities.go index ce95ee71..a59079da 100644 --- a/auditors/capabilities/capabilities.go +++ b/auditors/capabilities/capabilities.go @@ -76,7 +76,7 @@ func auditContainer(container *k8s.ContainerV1, capability string, allowAddList message := fmt.Sprintf("Capability \"%s\" added. It should be removed from the capability add list. If you need this capability, add an override label such as '%s: SomeReason'.", capability, override.GetContainerOverrideLabel(container.Name, getOverrideLabel(capability))) auditResult := &kubeaudit.AuditResult{ Auditor: Name, - Name: CapabilityAdded, + Rule: CapabilityAdded, Severity: kubeaudit.Error, Message: message, PendingFix: &fixCapabilityAdded{ @@ -105,7 +105,7 @@ func auditContainerForDropAll(container *k8s.ContainerV1) *kubeaudit.AuditResult message := "Security Context not set. The Security Context should be specified and all Capabilities should be dropped by setting the Drop list to ALL." return &kubeaudit.AuditResult{ Auditor: Name, - Name: CapabilityOrSecurityContextMissing, + Rule: CapabilityOrSecurityContextMissing, Severity: kubeaudit.Error, Message: message, PendingFix: &fixMissingSecurityContextOrCapability{ @@ -121,7 +121,7 @@ func auditContainerForDropAll(container *k8s.ContainerV1) *kubeaudit.AuditResult message := "Capability Drop list should be set to ALL. Add the specific ones you need to the Add list and set an override label." return &kubeaudit.AuditResult{ Auditor: Name, - Name: CapabilityShouldDropAll, + Rule: CapabilityShouldDropAll, Severity: kubeaudit.Error, Message: message, PendingFix: &fixCapabilityNotDroppedAll{ diff --git a/auditors/deprecatedapis/depreceatedapis.go b/auditors/deprecatedapis/depreceatedapis.go index f1bea0c4..60e67347 100644 --- a/auditors/deprecatedapis/depreceatedapis.go +++ b/auditors/deprecatedapis/depreceatedapis.go @@ -120,7 +120,7 @@ func (deprecatedAPIs *DeprecatedAPIs) Audit(resource k8s.Resource, _ []k8s.Resou } auditResult := &kubeaudit.AuditResult{ Auditor: Name, - Name: DeprecatedAPIUsed, + Rule: DeprecatedAPIUsed, Severity: severity, Message: deprecationMessage, Metadata: metadata, diff --git a/auditors/hostns/hostns.go b/auditors/hostns/hostns.go index 16264987..318f4da9 100644 --- a/auditors/hostns/hostns.go +++ b/auditors/hostns/hostns.go @@ -63,7 +63,7 @@ func auditHostNetwork(podSpec *k8s.PodSpecV1) *kubeaudit.AuditResult { } return &kubeaudit.AuditResult{ Auditor: Name, - Name: NamespaceHostNetworkTrue, + Rule: NamespaceHostNetworkTrue, Severity: kubeaudit.Error, Message: "hostNetwork is set to 'true' in PodSpec. It should be set to 'false'.", PendingFix: &fixHostNetworkTrue{ @@ -84,7 +84,7 @@ func auditHostIPC(podSpec *k8s.PodSpecV1) *kubeaudit.AuditResult { } return &kubeaudit.AuditResult{ Auditor: Name, - Name: NamespaceHostIPCTrue, + Rule: NamespaceHostIPCTrue, Severity: kubeaudit.Error, Message: "hostIPC is set to 'true' in PodSpec. It should be set to 'false'.", PendingFix: &fixHostIPCTrue{ @@ -105,7 +105,7 @@ func auditHostPID(podSpec *k8s.PodSpecV1) *kubeaudit.AuditResult { } return &kubeaudit.AuditResult{ Auditor: Name, - Name: NamespaceHostPIDTrue, + Rule: NamespaceHostPIDTrue, Severity: kubeaudit.Error, Message: "hostPID is set to 'true' in PodSpec. It should be set to 'false'.", PendingFix: &fixHostPIDTrue{ diff --git a/auditors/image/image.go b/auditors/image/image.go index 1678481e..5059fdc3 100644 --- a/auditors/image/image.go +++ b/auditors/image/image.go @@ -51,7 +51,7 @@ func auditContainer(container *k8s.ContainerV1, image string) *kubeaudit.AuditRe if isImageTagMissing(containerTag) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: ImageTagMissing, + Rule: ImageTagMissing, Severity: kubeaudit.Warn, Message: "Image tag is missing.", Metadata: kubeaudit.Metadata{ @@ -63,7 +63,7 @@ func auditContainer(container *k8s.ContainerV1, image string) *kubeaudit.AuditRe if isImageTagIncorrect(name, tag, containerName, containerTag) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: ImageTagIncorrect, + Rule: ImageTagIncorrect, Severity: kubeaudit.Error, Message: fmt.Sprintf("Container tag is incorrect. It should be set to '%s'.", tag), Metadata: kubeaudit.Metadata{ @@ -75,7 +75,7 @@ func auditContainer(container *k8s.ContainerV1, image string) *kubeaudit.AuditRe if isImageCorrect(name, tag, containerName, containerTag) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: ImageCorrect, + Rule: ImageCorrect, Severity: kubeaudit.Info, Message: "Image tag is correct", Metadata: kubeaudit.Metadata{ diff --git a/auditors/limits/limits.go b/auditors/limits/limits.go index 0b7988fa..7ad46ff8 100644 --- a/auditors/limits/limits.go +++ b/auditors/limits/limits.go @@ -66,7 +66,7 @@ func (limits *Limits) auditContainer(container *k8s.ContainerV1) (auditResults [ if isLimitsNil(container) { auditResult := &kubeaudit.AuditResult{ Auditor: Name, - Name: LimitsNotSet, + Rule: LimitsNotSet, Severity: kubeaudit.Warn, Message: "Resource limits not set.", Metadata: kubeaudit.Metadata{ @@ -83,7 +83,7 @@ func (limits *Limits) auditContainer(container *k8s.ContainerV1) (auditResults [ if isCPULimitUnset(container) { auditResult := &kubeaudit.AuditResult{ Auditor: Name, - Name: LimitsCPUNotSet, + Rule: LimitsCPUNotSet, Severity: kubeaudit.Warn, Message: "Resource CPU limit not set.", Metadata: kubeaudit.Metadata{ @@ -95,7 +95,7 @@ func (limits *Limits) auditContainer(container *k8s.ContainerV1) (auditResults [ maxCPU := limits.maxCPU.String() auditResult := &kubeaudit.AuditResult{ Auditor: Name, - Name: LimitsCPUExceeded, + Rule: LimitsCPUExceeded, Severity: kubeaudit.Warn, Message: fmt.Sprintf("CPU limit exceeded. It is set to '%s' which exceeds the max CPU limit of '%s'.", cpu, maxCPU), Metadata: kubeaudit.Metadata{ @@ -110,7 +110,7 @@ func (limits *Limits) auditContainer(container *k8s.ContainerV1) (auditResults [ if isMemoryLimitUnset(container) { auditResult := &kubeaudit.AuditResult{ Auditor: Name, - Name: LimitsMemoryNotSet, + Rule: LimitsMemoryNotSet, Severity: kubeaudit.Warn, Message: "Resource Memory limit not set.", Metadata: kubeaudit.Metadata{ @@ -122,7 +122,7 @@ func (limits *Limits) auditContainer(container *k8s.ContainerV1) (auditResults [ maxMemory := limits.maxMemory.String() auditResult := &kubeaudit.AuditResult{ Auditor: Name, - Name: LimitsMemoryExceeded, + Rule: LimitsMemoryExceeded, Severity: kubeaudit.Warn, Message: fmt.Sprintf("Memory limit exceeded. It is set to '%s' which exceeds the max Memory limit of '%s'.", memory, maxMemory), Metadata: kubeaudit.Metadata{ diff --git a/auditors/mounts/mounts.go b/auditors/mounts/mounts.go index efac26e0..104db4ef 100644 --- a/auditors/mounts/mounts.go +++ b/auditors/mounts/mounts.go @@ -101,7 +101,7 @@ func auditContainer(container *k8s.ContainerV1, sensitiveVolumes map[string]v1.V if volume, ok := sensitiveVolumes[mount.Name]; ok { auditResults = append(auditResults, &kubeaudit.AuditResult{ Auditor: Name, - Name: SensitivePathsMounted, + Rule: SensitivePathsMounted, Severity: kubeaudit.Error, Message: fmt.Sprintf("Sensitive path mounted as volume: %s (hostPath: %s). It should be removed from the container's mounts list.", mount.Name, volume.HostPath.Path), Metadata: kubeaudit.Metadata{ diff --git a/auditors/netpols/netpols.go b/auditors/netpols/netpols.go index 9c796709..233eb873 100644 --- a/auditors/netpols/netpols.go +++ b/auditors/netpols/netpols.go @@ -73,7 +73,7 @@ func auditNetworkPolicy(networkPolicy *k8s.NetworkPolicyV1) []*kubeaudit.AuditRe if allIngressTrafficAllowed(networkPolicy) { auditResult := &kubeaudit.AuditResult{ Auditor: Name, - Name: AllowAllIngressNetworkPolicyExists, + Rule: AllowAllIngressNetworkPolicyExists, Severity: kubeaudit.Warn, Message: "Found allow all ingress traffic NetworkPolicy.", Metadata: kubeaudit.Metadata{ @@ -86,7 +86,7 @@ func auditNetworkPolicy(networkPolicy *k8s.NetworkPolicyV1) []*kubeaudit.AuditRe if allEgressTrafficAllowed(networkPolicy) { auditResult := &kubeaudit.AuditResult{ Auditor: Name, - Name: AllowAllEgressNetworkPolicyExists, + Rule: AllowAllEgressNetworkPolicyExists, Severity: kubeaudit.Warn, Message: "Found allow all egress traffic NetworkPolicy.", Metadata: kubeaudit.Metadata{ @@ -111,7 +111,7 @@ func auditNetworkPoliciesForDenyAll(resource k8s.Resource, resources []k8s.Resou if !hasDefaultDenyIngress { auditResult := &kubeaudit.AuditResult{ Auditor: Name, - Name: MissingDefaultDenyIngressNetworkPolicy, + Rule: MissingDefaultDenyIngressNetworkPolicy, Severity: kubeaudit.Error, Message: fmt.Sprintf("All ingress traffic should be blocked by default for namespace %s.", namespace), Metadata: kubeaudit.Metadata{ @@ -129,7 +129,7 @@ func auditNetworkPoliciesForDenyAll(resource k8s.Resource, resources []k8s.Resou if !hasDefaultDenyEgress { auditResult := &kubeaudit.AuditResult{ Auditor: Name, - Name: MissingDefaultDenyEgressNetworkPolicy, + Rule: MissingDefaultDenyEgressNetworkPolicy, Severity: kubeaudit.Error, Message: fmt.Sprintf("All egress traffic should be blocked by default for namespace %s.", namespace), Metadata: kubeaudit.Metadata{ @@ -154,7 +154,7 @@ func auditNetworkPoliciesForDenyAll(resource k8s.Resource, resources []k8s.Resou if !hasIngressOverride && !hasEgressOverride { auditResult := &kubeaudit.AuditResult{ Auditor: Name, - Name: MissingDefaultDenyIngressAndEgressNetworkPolicy, + Rule: MissingDefaultDenyIngressAndEgressNetworkPolicy, Severity: kubeaudit.Error, Message: "Namespace is missing a default deny ingress and egress NetworkPolicy.", Metadata: kubeaudit.Metadata{ @@ -171,7 +171,7 @@ func auditNetworkPoliciesForDenyAll(resource k8s.Resource, resources []k8s.Resou if hasIngressOverride && hasEgressOverride { auditResult := &kubeaudit.AuditResult{ Auditor: Name, - Name: override.GetOverriddenResultName(MissingDefaultDenyIngressAndEgressNetworkPolicy), + Rule: override.GetOverriddenResultName(MissingDefaultDenyIngressAndEgressNetworkPolicy), Severity: kubeaudit.Warn, Message: "Namespace is missing a default deny ingress and egress NetworkPolicy.", Metadata: kubeaudit.Metadata{ @@ -186,7 +186,7 @@ func auditNetworkPoliciesForDenyAll(resource k8s.Resource, resources []k8s.Resou // fixed and the other is overridden auditResult := &kubeaudit.AuditResult{ Auditor: Name, - Name: MissingDefaultDenyIngressNetworkPolicy, + Rule: MissingDefaultDenyIngressNetworkPolicy, Severity: kubeaudit.Error, Message: "Namespace is missing a default deny ingress NetworkPolicy.", Metadata: kubeaudit.Metadata{ @@ -202,7 +202,7 @@ func auditNetworkPoliciesForDenyAll(resource k8s.Resource, resources []k8s.Resou auditResult = &kubeaudit.AuditResult{ Auditor: Name, - Name: MissingDefaultDenyEgressNetworkPolicy, + Rule: MissingDefaultDenyEgressNetworkPolicy, Severity: kubeaudit.Error, Message: "Namespace is missing a default deny egress NetworkPolicy.", Metadata: kubeaudit.Metadata{ diff --git a/auditors/nonroot/nonroot.go b/auditors/nonroot/nonroot.go index 83e0d9ed..7dba3e0a 100644 --- a/auditors/nonroot/nonroot.go +++ b/auditors/nonroot/nonroot.go @@ -57,7 +57,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if *container.SecurityContext.RunAsUser == 0 { return &kubeaudit.AuditResult{ Auditor: Name, - Name: RunAsUserCSCRoot, + Rule: RunAsUserCSCRoot, Severity: kubeaudit.Error, Message: "runAsUser is set to UID 0 (root user) in the container SecurityContext. Either set it to a value > 0 or remove it and set runAsNonRoot to true.", PendingFix: &fixRunAsNonRoot{ @@ -73,7 +73,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if *podSpec.SecurityContext.RunAsUser == 0 { return &kubeaudit.AuditResult{ Auditor: Name, - Name: RunAsUserPSCRoot, + Rule: RunAsUserPSCRoot, Severity: kubeaudit.Warn, Message: "runAsUser is set to UID 0 (root user) in the PodSecurityContext. Either set it to a value > 0 or remove it and set runAsNonRoot to true.", Metadata: kubeaudit.Metadata{ @@ -90,7 +90,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if *podSpec.SecurityContext.RunAsUser == 0 { return &kubeaudit.AuditResult{ Auditor: Name, - Name: RunAsUserPSCRoot, + Rule: RunAsUserPSCRoot, Severity: kubeaudit.Error, Message: "runAsUser is set to UID 0 (root user) in the PodSecurityContext. Either set it to a value > 0 or remove it and set runAsNonRoot to true.", PendingFix: &fixRunAsNonRoot{ @@ -108,7 +108,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isContainerRunAsNonRootCSCFalse(container) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: RunAsNonRootCSCFalse, + Rule: RunAsNonRootCSCFalse, Severity: kubeaudit.Error, Message: "runAsNonRoot is set to false in the container SecurityContext. Either set it to true or set runAsUser to a value > 0.", PendingFix: &fixRunAsNonRoot{ @@ -124,7 +124,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isPodRunAsNonRootNil(podSpec) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: RunAsNonRootPSCNilCSCNil, + Rule: RunAsNonRootPSCNilCSCNil, Severity: kubeaudit.Error, Message: "runAsNonRoot should be set to true or runAsUser should be set to a value > 0 either in the container SecurityContext or PodSecurityContext.", PendingFix: &fixRunAsNonRoot{ @@ -139,7 +139,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isPodRunAsNonRootFalse(podSpec) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: RunAsNonRootPSCFalseCSCNil, + Rule: RunAsNonRootPSCFalseCSCNil, Severity: kubeaudit.Error, Message: "runAsNonRoot is set to false in the PodSecurityContext. Either set it to true or set runAsUser to a value > 0.", PendingFix: &fixRunAsNonRoot{ diff --git a/auditors/privesc/privesc.go b/auditors/privesc/privesc.go index 531d236d..2833a56a 100644 --- a/auditors/privesc/privesc.go +++ b/auditors/privesc/privesc.go @@ -45,7 +45,7 @@ func auditContainer(container *k8s.ContainerV1) *kubeaudit.AuditResult { if isAllowPrivilegeEscalationNil(container) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: AllowPrivilegeEscalationNil, + Rule: AllowPrivilegeEscalationNil, Severity: kubeaudit.Error, Message: "allowPrivilegeEscalation not set which allows privilege escalation. It should be set to 'false'.", PendingFix: &fixBySettingAllowPrivilegeEscalationFalse{ @@ -60,7 +60,7 @@ func auditContainer(container *k8s.ContainerV1) *kubeaudit.AuditResult { if isAllowPrivilegeEscalationTrue(container) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: AllowPrivilegeEscalationTrue, + Rule: AllowPrivilegeEscalationTrue, Severity: kubeaudit.Error, Message: "allowPrivilegeEscalation set to 'true'. It should be set to 'false'.", PendingFix: &fixBySettingAllowPrivilegeEscalationFalse{ diff --git a/auditors/privileged/privileged.go b/auditors/privileged/privileged.go index f3b23efa..296d9dfe 100644 --- a/auditors/privileged/privileged.go +++ b/auditors/privileged/privileged.go @@ -44,7 +44,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isPrivilegedNil(container) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: PrivilegedNil, + Rule: PrivilegedNil, Severity: kubeaudit.Warn, Message: "privileged is not set in container SecurityContext. Privileged defaults to 'false' but it should be explicitly set to 'false'.", PendingFix: &fixPrivileged{ @@ -59,7 +59,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isPrivilegedTrue(container) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: PrivilegedTrue, + Rule: PrivilegedTrue, Severity: kubeaudit.Error, Message: "privileged is set to 'true' in container SecurityContext. It should be set to 'false'.", PendingFix: &fixPrivileged{ diff --git a/auditors/rootfs/rootfs.go b/auditors/rootfs/rootfs.go index 319fecc9..5f71eb5b 100644 --- a/auditors/rootfs/rootfs.go +++ b/auditors/rootfs/rootfs.go @@ -44,7 +44,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isReadOnlyRootFilesystemNil(container) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: ReadOnlyRootFilesystemNil, + Rule: ReadOnlyRootFilesystemNil, Severity: kubeaudit.Error, Message: "readOnlyRootFilesystem is not set in container SecurityContext. It should be set to 'true'.", PendingFix: &fixReadOnlyRootFilesystem{ @@ -59,7 +59,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isReadOnlyRootFilesystemFalse(container) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: ReadOnlyRootFilesystemFalse, + Rule: ReadOnlyRootFilesystemFalse, Severity: kubeaudit.Error, Message: "readOnlyRootFilesystem is set to 'false' in container SecurityContext. It should be set to 'true'.", PendingFix: &fixReadOnlyRootFilesystem{ diff --git a/auditors/seccomp/seccomp.go b/auditors/seccomp/seccomp.go index 91ab760d..3f01d319 100644 --- a/auditors/seccomp/seccomp.go +++ b/auditors/seccomp/seccomp.go @@ -77,7 +77,7 @@ func auditPod(resource k8s.Resource) *kubeaudit.AuditResult { return &kubeaudit.AuditResult{ Auditor: Name, - Name: SeccompAnnotationMissing, + Rule: SeccompAnnotationMissing, Severity: kubeaudit.Error, Message: fmt.Sprintf("Seccomp annotation is missing. The annotation %s: %s should be added.", PodAnnotationKey, ProfileRuntimeDefault), PendingFix: &fix.ByAddingPodAnnotation{ @@ -95,7 +95,7 @@ func auditPod(resource k8s.Resource) *kubeaudit.AuditResult { if isSeccompProfileDeprecated(podSeccompProfile) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: SeccompDeprecatedPod, + Rule: SeccompDeprecatedPod, Severity: kubeaudit.Error, Message: fmt.Sprintf("Seccomp pod annotation is set to deprecated value %s. It should be set to %s instead.", podSeccompProfile, ProfileRuntimeDefault), PendingFix: &fix.BySettingPodAnnotation{ @@ -112,7 +112,7 @@ func auditPod(resource k8s.Resource) *kubeaudit.AuditResult { if !isSeccompEnabled(podSeccompProfile) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: SeccompDisabledPod, + Rule: SeccompDisabledPod, Severity: kubeaudit.Error, Message: fmt.Sprintf("Seccomp pod annotation is set to %s which disables Seccomp. It should be set to the default profile %s or should start with %s.", podSeccompProfile, ProfileRuntimeDefault, ProfileNamePrefix), PendingFix: &fix.BySettingPodAnnotation{ @@ -158,7 +158,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if isSeccompProfileDeprecated(containerSeccompProfile) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: SeccompDeprecatedContainer, + Rule: SeccompDeprecatedContainer, Severity: kubeaudit.Error, Message: fmt.Sprintf("Seccomp container annotation is set to deprecated value %s. It should be set to %s instead.", containerSeccompProfile, ProfileRuntimeDefault), PendingFix: pendingFix, @@ -172,7 +172,7 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi if !isSeccompEnabled(containerSeccompProfile) { return &kubeaudit.AuditResult{ Auditor: Name, - Name: SeccompDisabledContainer, + Rule: SeccompDisabledContainer, Severity: kubeaudit.Error, Message: fmt.Sprintf("Seccomp container annotation is set to %s which disables Seccomp. It should be set to the default profile %s or should start with %s.", containerSeccompProfile, ProfileRuntimeDefault, ProfileNamePrefix), PendingFix: pendingFix, diff --git a/cmd/commands/root.go b/cmd/commands/root.go index 29140c84..c22ac36e 100644 --- a/cmd/commands/root.go +++ b/cmd/commands/root.go @@ -81,7 +81,10 @@ func runAudit(auditable ...kubeaudit.Auditable) func(cmd *cobra.Command, args [] } if rootConfig.sarifOut != "" { - sarifReport, sarifRun := sarif.CreateSarifReport() + sarifReport, sarifRun, err := sarif.CreateSarifReport() + if err != nil { + panic(err) + } sarif.AddSarifRules(report, sarifRun) sarif.AddSarifResult(report, sarifRun) sarifReport.WriteFile(rootConfig.sarifOut) diff --git a/example_custom_test.go b/example_custom_test.go index 44e7cefd..1c8a5a5c 100644 --- a/example_custom_test.go +++ b/example_custom_test.go @@ -29,7 +29,7 @@ type myAuditor struct{} func (a *myAuditor) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudit.AuditResult, error) { return []*kubeaudit.AuditResult{ { - Name: "MyAudit", + Rule: "MyAudit", Severity: kubeaudit.Error, Message: "My custom error", PendingFix: &myAuditorFix{ diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go index 14abd79b..71cb55cb 100644 --- a/internal/sarif/sarif.go +++ b/internal/sarif/sarif.go @@ -37,11 +37,12 @@ var AuditorNames = map[string]string{ seccomp.Name: "Finds containers running without seccomp", } -func CreateSarifReport() (*sarif.Report, *sarif.Run) { +// CreateSarifReport creates a new sarif Report and Run or returns an error +func CreateSarifReport() (*sarif.Report, *sarif.Run, error) { // create a new report object report, err := sarif.New(sarif.Version210) if err != nil { - panic(err) + return nil, nil, err } // create a run for kubeaudit @@ -49,7 +50,7 @@ func CreateSarifReport() (*sarif.Report, *sarif.Run) { report.AddRun(run) - return report, run + return report, run, nil } func AddSarifRules(kubeauditReport *kubeaudit.Report, run *sarif.Run) { @@ -62,16 +63,16 @@ func AddSarifRules(kubeauditReport *kubeaudit.Report, run *sarif.Run) { for _, result := range results { auditor := strings.ToLower(result.Auditor) - ruleID := strings.ToLower(result.Name) + ruleID := strings.ToLower(result.Rule) var docsURL string - // create a new rule for each rule id + if strings.Contains(ruleID, auditor) { docsURL = "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/" + auditor + ".md" } helpMessage := fmt.Sprintf("**Type**: kubernetes\n**Docs**: %s\n**Description:** %s", docsURL, AuditorNames[auditor]) - run.AddRule(result.Name). + run.AddRule(result.Rule). WithName(result.Auditor). WithMarkdownHelp(helpMessage). WithProperties(sarif.Properties{ @@ -106,7 +107,7 @@ func AddSarifResult(kubeauditReport *kubeaudit.Report, run *sarif.Run) { location := sarif.NewPhysicalLocation(). WithArtifactLocation(sarif.NewSimpleArtifactLocation(r.FilePath).WithUriBaseId("ROOTPATH")). WithRegion(sarif.NewRegion().WithStartLine(1)) - result := sarif.NewRuleResult(r.Name). + result := sarif.NewRuleResult(r.Rule). WithMessage(sarif.NewTextMessage(r.Message)). WithLevel(severityLevel). WithLocations([]*sarif.Location{sarif.NewLocation().WithPhysicalLocation(location)}) diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index c22d41c0..07401d3e 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -13,7 +13,8 @@ import ( ) func TestCreateSarifReport(t *testing.T) { - sarifReport, _ := CreateSarifReport() + sarifReport, _, err := CreateSarifReport() + require.NoError(t, err) require.Len(t, sarifReport.Runs, 1) assert.Equal(t, "https://github.com/Shopify/kubeaudit", *sarifReport.Runs[0].Tool.Driver.InformationURI) } @@ -55,7 +56,9 @@ func TestAddSarifResultToReport(t *testing.T) { // verify that the file path is correct assert.Contains(t, kubeAuditReport.Results()[0].GetAuditResults()[0].FilePath, "sarif/fixtures") - sarifReport, sarifRun := CreateSarifReport() + sarifReport, sarifRun, err := CreateSarifReport() + + require.NoError(t, err) AddSarifRules(kubeAuditReport, sarifRun) diff --git a/internal/test/test.go b/internal/test/test.go index ecf86f6a..bf22435d 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -46,7 +46,7 @@ func AuditMultiple(t *testing.T, fixtureDir, fixture string, auditables []kubeau errors := make(map[string]bool) for _, result := range report.Results() { for _, auditResult := range result.GetAuditResults() { - errors[auditResult.Name] = true + errors[auditResult.Rule] = true } } diff --git a/pkg/override/override.go b/pkg/override/override.go index ce099085..c1318d22 100644 --- a/pkg/override/override.go +++ b/pkg/override/override.go @@ -26,7 +26,7 @@ func GetOverriddenResultName(resultName string) string { // label because there are no security issues found, so the label is redundant func NewRedundantOverrideResult(containerName string, overrideReason, overrideLabel string) *kubeaudit.AuditResult { return &kubeaudit.AuditResult{ - Name: kubeaudit.RedundantAuditorOverride, + Rule: kubeaudit.RedundantAuditorOverride, Severity: kubeaudit.Warn, Message: "Auditor is disabled via label but there were no security issues found by the auditor. The label should be removed.", Metadata: kubeaudit.Metadata{ @@ -49,7 +49,7 @@ func ApplyOverride(auditResult *kubeaudit.AuditResult, containerName string, res return NewRedundantOverrideResult(containerName, overrideReason, overrideLabel) } - auditResult.Name = GetOverriddenResultName(auditResult.Name) + auditResult.Rule = GetOverriddenResultName(auditResult.Rule) auditResult.PendingFix = nil auditResult.Severity = kubeaudit.Info auditResult.Message = "Audit result overridden: " + auditResult.Message diff --git a/printer.go b/printer.go index 437e656a..538276a0 100644 --- a/printer.go +++ b/printer.go @@ -109,7 +109,7 @@ func (p *Printer) prettyPrintReport(report *Report) { } p.print("-- ") p.printColor(severityColor, "["+auditResult.Severity.String()+"] ") - p.print(auditResult.Name + "\n") + p.print(auditResult.Rule + "\n") p.print(" Message: " + auditResult.Message + "\n") if len(auditResult.Metadata) > 0 { p.print(" Metadata:\n") @@ -166,7 +166,7 @@ func (p *Printer) getLogFieldsForResult(resource k8s.Resource, result *AuditResu objectMeta := k8s.GetObjectMeta(resource) fields := log.Fields{ - "AuditResultName": result.Name, + "AuditResultName": result.Rule, "ResourceKind": kind, "ResourceApiVersion": apiVersion, } diff --git a/result.go b/result.go index e769b3c1..dd941bb8 100644 --- a/result.go +++ b/result.go @@ -38,7 +38,7 @@ func (s SeverityLevel) String() string { // AuditResult represents a potential security issue. There may be multiple AuditResults per resource and audit type AuditResult struct { Auditor string // auditor name - Name string // Name uniquely identifies a type of audit result + Rule string // Rule uniquely identifies a type of violation Severity SeverityLevel // Severity is one of Error, Warn, or Info Message string // Message is a human-readable description of the audit result PendingFix PendingFix // PendingFix is the fix that will be applied to automatically fix the security issue diff --git a/util_test.go b/util_test.go index bce54c8d..2a3b46fc 100644 --- a/util_test.go +++ b/util_test.go @@ -56,7 +56,7 @@ func TestPrintResults(t *testing.T) { func newTestAuditResult(severity SeverityLevel) *AuditResult { return &AuditResult{ - Name: "MyAuditResult", + Rule: "MyAuditResult", Severity: severity, Metadata: Metadata{"Foo": "bar"}, } From 69491eaea83c8f3762c27066beda43914c3d7b7d Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Mon, 11 Jul 2022 12:07:13 -0400 Subject: [PATCH 06/31] removes extra function with duplicate logic --- cmd/commands/root.go | 5 +- .../sarif/fixtures/apparmor-disabled.yaml | 4 -- .../sarif/fixtures/capabilities-added.yaml | 5 -- internal/sarif/sarif.go | 36 +++++--------- internal/sarif/sarif_test.go | 49 ++++++++++++------- 5 files changed, 45 insertions(+), 54 deletions(-) diff --git a/cmd/commands/root.go b/cmd/commands/root.go index c22ac36e..55ea53fb 100644 --- a/cmd/commands/root.go +++ b/cmd/commands/root.go @@ -81,12 +81,11 @@ func runAudit(auditable ...kubeaudit.Auditable) func(cmd *cobra.Command, args [] } if rootConfig.sarifOut != "" { - sarifReport, sarifRun, err := sarif.CreateSarifReport() + sarifReport, sarifRun, err := sarif.New() if err != nil { panic(err) } - sarif.AddSarifRules(report, sarifRun) - sarif.AddSarifResult(report, sarifRun) + sarif.Create(report, sarifRun) sarifReport.WriteFile(rootConfig.sarifOut) } diff --git a/internal/sarif/fixtures/apparmor-disabled.yaml b/internal/sarif/fixtures/apparmor-disabled.yaml index b2593ab1..a67bf336 100644 --- a/internal/sarif/fixtures/apparmor-disabled.yaml +++ b/internal/sarif/fixtures/apparmor-disabled.yaml @@ -5,7 +5,3 @@ metadata: namespace: apparmor-disabled annotations: container.apparmor.security.beta.kubernetes.io/container: badval -spec: - containers: - - name: container - image: scratch diff --git a/internal/sarif/fixtures/capabilities-added.yaml b/internal/sarif/fixtures/capabilities-added.yaml index f5138302..a504c250 100644 --- a/internal/sarif/fixtures/capabilities-added.yaml +++ b/internal/sarif/fixtures/capabilities-added.yaml @@ -1,10 +1,5 @@ apiVersion: apps/v1 kind: Deployment -metadata: - name: deployment - namespace: capabilities-added - annotations: - container.apparmor.security.beta.kubernetes.io/container: badval spec: selector: matchLabels: diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go index 71cb55cb..f65825fa 100644 --- a/internal/sarif/sarif.go +++ b/internal/sarif/sarif.go @@ -21,7 +21,7 @@ import ( "github.com/owenrumney/go-sarif/v2/sarif" ) -var AuditorNames = map[string]string{ +var Auditors = map[string]string{ apparmor.Name: "Finds containers that do not have AppArmor enabled", asat.Name: "Finds containers where the deprecated SA field is used or with a mounted default SA", capabilities.Name: "Finds containers that do not drop the recommended capabilities or add new ones", @@ -37,8 +37,8 @@ var AuditorNames = map[string]string{ seccomp.Name: "Finds containers running without seccomp", } -// CreateSarifReport creates a new sarif Report and Run or returns an error -func CreateSarifReport() (*sarif.Report, *sarif.Run, error) { +// New creates a new sarif Report and Run or returns an error +func New() (*sarif.Report, *sarif.Run, error) { // create a new report object report, err := sarif.New(sarif.Version210) if err != nil { @@ -53,7 +53,8 @@ func CreateSarifReport() (*sarif.Report, *sarif.Run, error) { return report, run, nil } -func AddSarifRules(kubeauditReport *kubeaudit.Report, run *sarif.Run) { +// Create adds SARIF rules to the run and adds results to the report +func Create(kubeauditReport *kubeaudit.Report, run *sarif.Run) { var results []*kubeaudit.AuditResult for _, reportResult := range kubeauditReport.Results() { @@ -62,16 +63,18 @@ func AddSarifRules(kubeauditReport *kubeaudit.Report, run *sarif.Run) { } for _, result := range results { + severityLevel := result.Severity.String() auditor := strings.ToLower(result.Auditor) ruleID := strings.ToLower(result.Rule) - var docsURL string + var docsURL string if strings.Contains(ruleID, auditor) { docsURL = "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/" + auditor + ".md" } - helpMessage := fmt.Sprintf("**Type**: kubernetes\n**Docs**: %s\n**Description:** %s", docsURL, AuditorNames[auditor]) + helpMessage := fmt.Sprintf("**Type**: kubernetes\n**Docs**: %s\n**Description:** %s", docsURL, Auditors[auditor]) + // we only add rules to the report based on the result findings run.AddRule(result.Rule). WithName(result.Auditor). WithMarkdownHelp(helpMessage). @@ -83,32 +86,19 @@ func AddSarifRules(kubeauditReport *kubeaudit.Report, run *sarif.Run) { }, "precision": "very-high", }) - } -} - -func AddSarifResult(kubeauditReport *kubeaudit.Report, run *sarif.Run) { - var results []*kubeaudit.AuditResult - - for _, reportResult := range kubeauditReport.Results() { - r := reportResult.GetAuditResults() - results = append(results, r...) - } - - for _, r := range results { - severityLevel := r.Severity.String() // SARIF specifies the following severity levels: warning, error, note and none // https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html // so we're converting info to none here so we get valid SARIF output - if r.Severity.String() == "info" { + if result.Severity.String() == "info" { severityLevel = "note" } location := sarif.NewPhysicalLocation(). - WithArtifactLocation(sarif.NewSimpleArtifactLocation(r.FilePath).WithUriBaseId("ROOTPATH")). + WithArtifactLocation(sarif.NewSimpleArtifactLocation(result.FilePath).WithUriBaseId("ROOTPATH")). WithRegion(sarif.NewRegion().WithStartLine(1)) - result := sarif.NewRuleResult(r.Rule). - WithMessage(sarif.NewTextMessage(r.Message)). + result := sarif.NewRuleResult(result.Rule). + WithMessage(sarif.NewTextMessage(result.Message)). WithLevel(severityLevel). WithLocations([]*sarif.Location{sarif.NewLocation().WithPhysicalLocation(location)}) run.AddResult(result) diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index 07401d3e..b856be31 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -8,34 +8,49 @@ import ( "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/auditors/apparmor" "github.com/Shopify/kubeaudit/auditors/capabilities" + "github.com/Shopify/kubeaudit/auditors/seccomp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestCreateSarifReport(t *testing.T) { - sarifReport, _, err := CreateSarifReport() +func TestNew(t *testing.T) { + sarifReport, _, err := New() require.NoError(t, err) require.Len(t, sarifReport.Runs, 1) assert.Equal(t, "https://github.com/Shopify/kubeaudit", *sarifReport.Runs[0].Tool.Driver.InformationURI) } -func TestAddSarifResultToReport(t *testing.T) { +func TestCreate(t *testing.T) { capabilitiesAuditable := capabilities.New(capabilities.Config{}) apparmorAuditable := apparmor.New() - - auditables := []kubeaudit.Auditable{capabilitiesAuditable, apparmorAuditable} + seccompAuditable := seccomp.New() cases := []struct { - file string - auditor string + file string + auditorName string + auditors []kubeaudit.Auditable + expectedRules int }{ - {"apparmor-disabled.yaml", apparmor.Name}, - {"capabilities-added.yaml", capabilities.Name}, + {"apparmor-disabled.yaml", + apparmor.Name, + []kubeaudit.Auditable{apparmorAuditable}, + 1, + }, + {"capabilities-added.yaml", + capabilities.Name, + []kubeaudit.Auditable{capabilitiesAuditable, seccompAuditable}, + 2, + }, + {"capabilities-added.yaml", + capabilities.Name, + []kubeaudit.Auditable{capabilitiesAuditable}, + 1, + }, } for _, tc := range cases { fixture := filepath.Join("fixtures", tc.file) - auditor, err := kubeaudit.New(auditables) + auditor, err := kubeaudit.New(tc.auditors) require.NoError(t, err) manifest, openErr := os.Open(fixture) @@ -45,6 +60,7 @@ func TestAddSarifResultToReport(t *testing.T) { require.NoError(t, err) // we're only appending sarif to the path here for testing purposes + // this allows us to visualize the sarif output preview correctly for _, reportResult := range kubeAuditReport.Results() { r := reportResult.GetAuditResults() @@ -56,17 +72,12 @@ func TestAddSarifResultToReport(t *testing.T) { // verify that the file path is correct assert.Contains(t, kubeAuditReport.Results()[0].GetAuditResults()[0].FilePath, "sarif/fixtures") - sarifReport, sarifRun, err := CreateSarifReport() - + sarifReport, sarifRun, err := New() require.NoError(t, err) - AddSarifRules(kubeAuditReport, sarifRun) + Create(kubeAuditReport, sarifRun) - // verify that the 2 rules (auditors) enabled have been added to the report - require.Len(t, *&sarifReport.Runs[0].Tool.Driver.Rules, 2) - - AddSarifResult(kubeAuditReport, sarifRun) + // verify that the rules have been added as per report findings + assert.Len(t, sarifReport.Runs[0].Tool.Driver.Rules, tc.expectedRules) } } - -// todo: verify the contents of the report From 56f6269a3aebeb35567e3cd908a4e18ce6712041 Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Mon, 11 Jul 2022 23:30:52 -0400 Subject: [PATCH 07/31] tests rule occurrences --- internal/sarif/sarif_test.go | 31 ++++++++++++++++++++++--------- profile.out | 12 ++++++++++++ 2 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 profile.out diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index b856be31..eee47898 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -17,7 +17,8 @@ func TestNew(t *testing.T) { sarifReport, _, err := New() require.NoError(t, err) require.Len(t, sarifReport.Runs, 1) - assert.Equal(t, "https://github.com/Shopify/kubeaudit", *sarifReport.Runs[0].Tool.Driver.InformationURI) + assert.Equal(t, "https://github.com/Shopify/kubeaudit", + *sarifReport.Runs[0].Tool.Driver.InformationURI) } func TestCreate(t *testing.T) { @@ -26,25 +27,32 @@ func TestCreate(t *testing.T) { seccompAuditable := seccomp.New() cases := []struct { - file string - auditorName string - auditors []kubeaudit.Auditable - expectedRules int + file string + auditorName string + auditors []kubeaudit.Auditable + expectedRuleCount int + expectedRules []string }{ - {"apparmor-disabled.yaml", + { + "apparmor-disabled.yaml", apparmor.Name, []kubeaudit.Auditable{apparmorAuditable}, 1, + []string{"AppArmorInvalidAnnotation"}, }, - {"capabilities-added.yaml", + { + "capabilities-added.yaml", capabilities.Name, []kubeaudit.Auditable{capabilitiesAuditable, seccompAuditable}, 2, + []string{"CapabilityAdded, SeccompAnnotationMissing"}, }, - {"capabilities-added.yaml", + { + "capabilities-added.yaml", capabilities.Name, []kubeaudit.Auditable{capabilitiesAuditable}, 1, + []string{"CapabilityAdded"}, }, } @@ -78,6 +86,11 @@ func TestCreate(t *testing.T) { Create(kubeAuditReport, sarifRun) // verify that the rules have been added as per report findings - assert.Len(t, sarifReport.Runs[0].Tool.Driver.Rules, tc.expectedRules) + assert.Len(t, sarifReport.Runs[0].Tool.Driver.Rules, tc.expectedRuleCount) + + // check for rules occurrences + for _, expectedRule := range tc.expectedRules { + assert.Contains(t, expectedRule, sarifReport.Runs[0].Tool.Driver.Rules) + } } } diff --git a/profile.out b/profile.out new file mode 100644 index 00000000..e7cd5134 --- /dev/null +++ b/profile.out @@ -0,0 +1,12 @@ +mode: atomic +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:41.47,44.16 2 4 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:49.2,53.25 3 4 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:44.16,46.3 1 0 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:57.64,60.57 2 3 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:65.2,65.33 1 3 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:60.57,63.3 2 3 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:65.33,71.40 5 6 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:75.3,93.41 3 6 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:97.3,104.24 3 6 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:71.40,73.4 1 2 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:93.41,95.4 1 0 From dfd6b54e5f0d7099eabf868b49389b385ed92cec Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Tue, 12 Jul 2022 10:41:25 -0400 Subject: [PATCH 08/31] refactor sarif pkg and fix broken tests --- cmd/commands/root.go | 7 ++++--- internal/sarif/sarif.go | 13 +++++------- internal/sarif/sarif_test.go | 38 +++++++++++++++--------------------- 3 files changed, 25 insertions(+), 33 deletions(-) diff --git a/cmd/commands/root.go b/cmd/commands/root.go index 55ea53fb..c759bc8a 100644 --- a/cmd/commands/root.go +++ b/cmd/commands/root.go @@ -81,11 +81,12 @@ func runAudit(auditable ...kubeaudit.Auditable) func(cmd *cobra.Command, args [] } if rootConfig.sarifOut != "" { - sarifReport, sarifRun, err := sarif.New() + sarifReport, err := sarif.Create(report) + if err != nil { - panic(err) + log.WithError(err).Fatal("error generating the SARIF output") } - sarif.Create(report, sarifRun) + sarifReport.WriteFile(rootConfig.sarifOut) } diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go index f65825fa..fb5c4bbb 100644 --- a/internal/sarif/sarif.go +++ b/internal/sarif/sarif.go @@ -37,12 +37,12 @@ var Auditors = map[string]string{ seccomp.Name: "Finds containers running without seccomp", } -// New creates a new sarif Report and Run or returns an error -func New() (*sarif.Report, *sarif.Run, error) { +// Create generates new sarif Report or returns an error +func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { // create a new report object report, err := sarif.New(sarif.Version210) if err != nil { - return nil, nil, err + return nil, err } // create a run for kubeaudit @@ -50,11 +50,6 @@ func New() (*sarif.Report, *sarif.Run, error) { report.AddRun(run) - return report, run, nil -} - -// Create adds SARIF rules to the run and adds results to the report -func Create(kubeauditReport *kubeaudit.Report, run *sarif.Run) { var results []*kubeaudit.AuditResult for _, reportResult := range kubeauditReport.Results() { @@ -103,4 +98,6 @@ func Create(kubeauditReport *kubeaudit.Report, run *sarif.Run) { WithLocations([]*sarif.Location{sarif.NewLocation().WithPhysicalLocation(location)}) run.AddResult(result) } + + return report, nil } diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index eee47898..e08a94e6 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -13,45 +13,33 @@ import ( "github.com/stretchr/testify/require" ) -func TestNew(t *testing.T) { - sarifReport, _, err := New() - require.NoError(t, err) - require.Len(t, sarifReport.Runs, 1) - assert.Equal(t, "https://github.com/Shopify/kubeaudit", - *sarifReport.Runs[0].Tool.Driver.InformationURI) -} - func TestCreate(t *testing.T) { capabilitiesAuditable := capabilities.New(capabilities.Config{}) apparmorAuditable := apparmor.New() seccompAuditable := seccomp.New() cases := []struct { - file string - auditorName string - auditors []kubeaudit.Auditable - expectedRuleCount int - expectedRules []string + file string + auditorName string + auditors []kubeaudit.Auditable + expectedRules []string }{ { "apparmor-disabled.yaml", apparmor.Name, []kubeaudit.Auditable{apparmorAuditable}, - 1, []string{"AppArmorInvalidAnnotation"}, }, { "capabilities-added.yaml", capabilities.Name, []kubeaudit.Auditable{capabilitiesAuditable, seccompAuditable}, - 2, - []string{"CapabilityAdded, SeccompAnnotationMissing"}, + []string{"CapabilityAdded", "SeccompAnnotationMissing"}, }, { "capabilities-added.yaml", capabilities.Name, []kubeaudit.Auditable{capabilitiesAuditable}, - 1, []string{"CapabilityAdded"}, }, } @@ -75,22 +63,28 @@ func TestCreate(t *testing.T) { for _, auditResult := range r { auditResult.FilePath = filepath.Join("sarif/", auditResult.FilePath) } - } + } // verify that the file path is correct assert.Contains(t, kubeAuditReport.Results()[0].GetAuditResults()[0].FilePath, "sarif/fixtures") - sarifReport, sarifRun, err := New() + sarifReport, err := Create(kubeAuditReport) require.NoError(t, err) - Create(kubeAuditReport, sarifRun) + assert.Equal(t, "https://github.com/Shopify/kubeaudit", + *sarifReport.Runs[0].Tool.Driver.InformationURI) // verify that the rules have been added as per report findings - assert.Len(t, sarifReport.Runs[0].Tool.Driver.Rules, tc.expectedRuleCount) + assert.Len(t, sarifReport.Runs[0].Tool.Driver.Rules, len(tc.expectedRules)) + var ruleNames []string // check for rules occurrences + for _, sarifRule := range sarifReport.Runs[0].Tool.Driver.Rules { + ruleNames = append(ruleNames, sarifRule.ID) + } + for _, expectedRule := range tc.expectedRules { - assert.Contains(t, expectedRule, sarifReport.Runs[0].Tool.Driver.Rules) + assert.Contains(t, ruleNames, expectedRule) } } } From a70ec3aecb6a5fe99878a8b6450fe64d51843adb Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Mon, 18 Jul 2022 17:27:03 -0400 Subject: [PATCH 09/31] adds more tests --- go.mod | 2 + go.sum | 4 + internal/sarif/fixtures/valid.sarif | 239 ++ internal/sarif/sarif-schema.json | 3349 +++++++++++++++++++++++++++ internal/sarif/sarif.go | 49 +- internal/sarif/sarif_test.go | 39 + 6 files changed, 3681 insertions(+), 1 deletion(-) create mode 100644 internal/sarif/fixtures/valid.sarif create mode 100644 internal/sarif/sarif-schema.json diff --git a/go.mod b/go.mod index de5d4a44..b4fab07a 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,8 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/qri-io/jsonpointer v0.1.1 // indirect + github.com/qri-io/jsonschema v0.2.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.4.0 // indirect golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect diff --git a/go.sum b/go.sum index 1adbee04..ddd02873 100644 --- a/go.sum +++ b/go.sum @@ -954,6 +954,10 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/qri-io/jsonpointer v0.1.1 h1:prVZBZLL6TW5vsSB9fFHFAMBLI4b0ri5vribQlTJiBA= +github.com/qri-io/jsonpointer v0.1.1/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64= +github.com/qri-io/jsonschema v0.2.1 h1:NNFoKms+kut6ABPf6xiKNM5214jzxAhDBrPHCJ97Wg0= +github.com/qri-io/jsonschema v0.2.1/go.mod h1:g7DPkiOsK1xv6T/Ao5scXRkd+yTFygcANPBaaqW+VrI= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= diff --git a/internal/sarif/fixtures/valid.sarif b/internal/sarif/fixtures/valid.sarif new file mode 100644 index 00000000..03f2dc42 --- /dev/null +++ b/internal/sarif/fixtures/valid.sarif @@ -0,0 +1,239 @@ +{ + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + "version": "2.1.0", + "runs": [{ + "tool": { + "driver": { + "name": "LGTM.com", + "organization": "Semmle", + "version": "1.24.0-SNAPSHOT", + "rules": [{ + "id": "js/unused-local-variable", + "name": "js/unused-local-variable", + "shortDescription": { + "text": "Unused variable, import, function or class" + }, + "fullDescription": { + "text": "Unused variables, imports, functions or classes may be a symptom of a bug and should be examined carefully." + }, + "defaultConfiguration": { + "level": "note" + }, + "properties": { + "tags": ["maintainability"], + "kind": "problem", + "precision": "very-high", + "name": "Unused variable, import, function or class", + "description": "Unused variables, imports, functions or classes may be a symptom of a bug\n and should be examined carefully.", + "id": "js/unused-local-variable", + "problem.severity": "recommendation" + } + }] + } + }, + "results": [{ + "ruleId": "js/unused-local-variable", + "ruleIndex": 0, + "message": { + "text": "Unused variable foo." + }, + "locations": [{ + "physicalLocation": { + "artifactLocation": { + "uri": "main.js", + "uriBaseId": "%SRCROOT%", + "index": 0 + }, + "region": { + "startLine": 2, + "startColumn": 7, + "endColumn": 10 + } + } + }], + "partialFingerprints": { + "primaryLocationLineHash": "39fa2ee980eb94b0:1", + "primaryLocationStartColumnFingerprint": "4" + } + }], + "columnKind": "utf16CodeUnits", + "properties": { + "semmle.formatSpecifier": "2.1.0", + "semmle.sourceLanguage": "java" + } + }, + { + "tool" : { + "driver" : { + "name" : "CodeQL command-line toolchain", + "organization" : "GitHub", + "semanticVersion" : "2.0.0", + "rules" : [ { + "id" : "js/unused-local-variable", + "name" : "js/unused-local-variable", + "shortDescription" : { + "text" : "Unused variable, import, function or class" + }, + "fullDescription" : { + "text" : "Unused variables, imports, functions or classes may be a symptom of a bug and should be examined carefully." + }, + "defaultConfiguration" : { + "level": "note" + }, + "properties" : { + "tags" : [ "maintainability" ], + "kind" : "problem", + "precision" : "very-high", + "name" : "Unused variable, import, function or class", + "description" : "Unused variables, imports, functions or classes may be a symptom of a bug\n and should be examined carefully.", + "id" : "js/unused-local-variable", + "problem.severity" : "recommendation" + } + }, + { + "id": "js/inconsistent-use-of-new", + "name": "js/inconsistent-use-of-new", + "shortDescription": { + "text": "Inconsistent use of 'new'" + }, + "fullDescription": { + "text": "If a function is intended to be a constructor, it should always be invoked with 'new'. Otherwise, it should always be invoked as a normal function, that is, without 'new'." + }, + "defaultConfiguration": { + "level": "note" + }, + "properties": { + "tags": [ + "reliability", + "correctness", + "language-features" + ], + "kind": "problem", + "precision": "very-high", + "problem.severity": "warning" + } + } ] + } + }, + "artifacts" : [ { + "location" : { + "uri" : "main.js", + "uriBaseId" : "%SRCROOT%", + "index" : 0 + } + }, + { + "location": { + "uri": "src/promiseUtils.js", + "uriBaseId": "%SRCROOT%", + "index": 1 + } + }, + { + "location": { + "uri": "src/LiveQueryClient.js", + "uriBaseId": "%SRCROOT%", + "index": 2 + } + }, + { + "location": { + "uri": "src/ParseObject.js", + "uriBaseId": "%SRCROOT%", + "index": 3 + } + } ], + "results" : [ { + "ruleId" : "js/unused-local-variable", + "ruleIndex" : 0, + "message" : { + "text" : "Unused variable foo." + }, + "locations" : [ { + "physicalLocation" : { + "artifactLocation" : { + "uri" : "main.js", + "uriBaseId" : "%SRCROOT%", + "index" : 0 + }, + "region" : { + "startLine" : 2, + "startColumn" : 7, + "endColumn" : 10 + } + } + } ], + "partialFingerprints" : { + "primaryLocationLineHash" : "39fa2ee980eb94b0:1", + "primaryLocationStartColumnFingerprint" : "4" + } + }, + { + "ruleId": "js/inconsistent-use-of-new", + "ruleIndex": 1, + "message": { + "text": "Function resolvingPromise is sometimes invoked as a constructor (for example [here](1)), and sometimes as a normal function (for example [here](2))." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/promiseUtils.js", + "uriBaseId": "%SRCROOT%", + "index": 1 + }, + "region": { + "startLine": 2 + } + } + } + ], + "partialFingerprints": { + "primaryLocationLineHash": "5061c3315a741b7d:1", + "primaryLocationStartColumnFingerprint": "7" + }, + "relatedLocations": [ + { + "id": 1, + "physicalLocation": { + "artifactLocation": { + "uri": "src/ParseObject.js", + "uriBaseId": "%SRCROOT%", + "index": 3 + }, + "region": { + "startLine": 2281, + "startColumn": 33, + "endColumn": 55 + } + }, + "message": { + "text": "here" + } + }, + { + "id": 2, + "physicalLocation": { + "artifactLocation": { + "uri": "src/LiveQueryClient.js", + "uriBaseId": "%SRCROOT%", + "index": 2 + }, + "region": { + "startLine": 166 + } + }, + "message": { + "text": "here" + } + } + ] + } ], + "newlineSequences" : [ "\r\n", "\n", "", "" ], + "columnKind" : "utf16CodeUnits", + "properties" : { + "semmle.formatSpecifier" : "sarif-latest" + } + } + ] +} diff --git a/internal/sarif/sarif-schema.json b/internal/sarif/sarif-schema.json new file mode 100644 index 00000000..41b12d4a --- /dev/null +++ b/internal/sarif/sarif-schema.json @@ -0,0 +1,3349 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Static Analysis Results Format (SARIF) Version 2.1.0 JSON Schema", + "$id": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + "description": "Static Analysis Results Format (SARIF) Version 2.1.0 JSON Schema: a standard format for the output of static analysis tools.", + "additionalProperties": false, + "type": "object", + "properties": { + + "$schema": { + "description": "The URI of the JSON schema corresponding to the version.", + "type": "string", + "format": "uri" + }, + + "version": { + "description": "The SARIF format version of this log file.", + "enum": [ "2.1.0" ] + }, + + "runs": { + "description": "The set of runs contained in this log file.", + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/definitions/run" + } + }, + + "inlineExternalProperties": { + "description": "References to external property files that share data between runs.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/externalProperties" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the log file.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "version", "runs" ], + + "definitions": { + + "address": { + "description": "A physical or virtual address, or a range of addresses, in an 'addressable region' (memory or a binary file).", + "additionalProperties": false, + "type": "object", + "properties": { + + "absoluteAddress": { + "description": "The address expressed as a byte offset from the start of the addressable region.", + "type": "integer", + "minimum": -1, + "default": -1 + + }, + + "relativeAddress": { + "description": "The address expressed as a byte offset from the absolute address of the top-most parent object.", + "type": "integer" + + }, + + "length": { + "description": "The number of bytes in this range of addresses.", + "type": "integer" + }, + + "kind": { + "description": "An open-ended string that identifies the address kind. 'data', 'function', 'header','instruction', 'module', 'page', 'section', 'segment', 'stack', 'stackFrame', 'table' are well-known values.", + "type": "string" + }, + + "name": { + "description": "A name that is associated with the address, e.g., '.text'.", + "type": "string" + }, + + "fullyQualifiedName": { + "description": "A human-readable fully qualified name that is associated with the address.", + "type": "string" + }, + + "offsetFromParent": { + "description": "The byte offset of this address from the absolute or relative address of the parent object.", + "type": "integer" + }, + + "index": { + "description": "The index within run.addresses of the cached object for this address.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "parentIndex": { + "description": "The index within run.addresses of the parent object.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the address.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "artifact": { + "description": "A single artifact. In some cases, this artifact might be nested within another artifact.", + "additionalProperties": false, + "type": "object", + "properties": { + + "description": { + "description": "A short description of the artifact.", + "$ref": "#/definitions/message" + }, + + "location": { + "description": "The location of the artifact.", + "$ref": "#/definitions/artifactLocation" + }, + + "parentIndex": { + "description": "Identifies the index of the immediate parent of the artifact, if this artifact is nested.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "offset": { + "description": "The offset in bytes of the artifact within its containing artifact.", + "type": "integer", + "minimum": 0 + }, + + "length": { + "description": "The length of the artifact in bytes.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "roles": { + "description": "The role or roles played by the artifact in the analysis.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "enum": [ + "analysisTarget", + "attachment", + "responseFile", + "resultFile", + "standardStream", + "tracedFile", + "unmodified", + "modified", + "added", + "deleted", + "renamed", + "uncontrolled", + "driver", + "extension", + "translation", + "taxonomy", + "policy", + "referencedOnCommandLine", + "memoryContents", + "directory", + "userSpecifiedConfiguration", + "toolSpecifiedConfiguration", + "debugOutputFile" + ] + } + }, + + "mimeType": { + "description": "The MIME type (RFC 2045) of the artifact.", + "type": "string", + "pattern": "[^/]+/.+" + }, + + "contents": { + "description": "The contents of the artifact.", + "$ref": "#/definitions/artifactContent" + }, + + "encoding": { + "description": "Specifies the encoding for an artifact object that refers to a text file.", + "type": "string" + }, + + "sourceLanguage": { + "description": "Specifies the source language for any artifact object that refers to a text file that contains source code.", + "type": "string" + }, + + "hashes": { + "description": "A dictionary, each of whose keys is the name of a hash function and each of whose values is the hashed value of the artifact produced by the specified hash function.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + + "lastModifiedTimeUtc": { + "description": "The Coordinated Universal Time (UTC) date and time at which the artifact was most recently modified. See \"Date/time properties\" in the SARIF spec for the required format.", + "type": "string", + "format": "date-time" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the artifact.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "artifactChange": { + "description": "A change to a single artifact.", + "additionalProperties": false, + "type": "object", + "properties": { + + "artifactLocation": { + "description": "The location of the artifact to change.", + "$ref": "#/definitions/artifactLocation" + }, + + "replacements": { + "description": "An array of replacement objects, each of which represents the replacement of a single region in a single artifact specified by 'artifactLocation'.", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/replacement" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the change.", + "$ref": "#/definitions/propertyBag" + } + + }, + + "required": [ "artifactLocation", "replacements" ] + }, + + "artifactContent": { + "description": "Represents the contents of an artifact.", + "type": "object", + "additionalProperties": false, + "properties": { + + "text": { + "description": "UTF-8-encoded content from a text artifact.", + "type": "string" + }, + + "binary": { + "description": "MIME Base64-encoded content from a binary artifact, or from a text artifact in its original encoding.", + "type": "string" + }, + + "rendered": { + "description": "An alternate rendered representation of the artifact (e.g., a decompiled representation of a binary region).", + "$ref": "#/definitions/multiformatMessageString" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the artifact content.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "artifactLocation": { + "description": "Specifies the location of an artifact.", + "additionalProperties": false, + "type": "object", + "properties": { + + "uri": { + "description": "A string containing a valid relative or absolute URI.", + "type": "string", + "format": "uri-reference" + }, + + "uriBaseId": { + "description": "A string which indirectly specifies the absolute URI with respect to which a relative URI in the \"uri\" property is interpreted.", + "type": "string" + }, + + "index": { + "description": "The index within the run artifacts array of the artifact object associated with the artifact location.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "description": { + "description": "A short description of the artifact location.", + "$ref": "#/definitions/message" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the artifact location.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "attachment": { + "description": "An artifact relevant to a result.", + "type": "object", + "additionalProperties": false, + "properties": { + + "description": { + "description": "A message describing the role played by the attachment.", + "$ref": "#/definitions/message" + }, + + "artifactLocation": { + "description": "The location of the attachment.", + "$ref": "#/definitions/artifactLocation" + }, + + "regions": { + "description": "An array of regions of interest within the attachment.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/region" + } + }, + + "rectangles": { + "description": "An array of rectangles specifying areas of interest within the image.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/rectangle" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the attachment.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "artifactLocation" ] + }, + + "codeFlow": { + "description": "A set of threadFlows which together describe a pattern of code execution relevant to detecting a result.", + "additionalProperties": false, + "type": "object", + "properties": { + + "message": { + "description": "A message relevant to the code flow.", + "$ref": "#/definitions/message" + }, + + "threadFlows": { + "description": "An array of one or more unique threadFlow objects, each of which describes the progress of a program through a thread of execution.", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/threadFlow" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the code flow.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "threadFlows" ] + }, + + "configurationOverride": { + "description": "Information about how a specific rule or notification was reconfigured at runtime.", + "type": "object", + "additionalProperties": false, + "properties": { + + "configuration": { + "description": "Specifies how the rule or notification was configured during the scan.", + "$ref": "#/definitions/reportingConfiguration" + }, + + "descriptor": { + "description": "A reference used to locate the descriptor whose configuration was overridden.", + "$ref": "#/definitions/reportingDescriptorReference" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the configuration override.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "configuration", "descriptor" ] + }, + + "conversion": { + "description": "Describes how a converter transformed the output of a static analysis tool from the analysis tool's native output format into the SARIF format.", + "additionalProperties": false, + "type": "object", + "properties": { + + "tool": { + "description": "A tool object that describes the converter.", + "$ref": "#/definitions/tool" + }, + + "invocation": { + "description": "An invocation object that describes the invocation of the converter.", + "$ref": "#/definitions/invocation" + }, + + "analysisToolLogFiles": { + "description": "The locations of the analysis tool's per-run log files.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/artifactLocation" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the conversion.", + "$ref": "#/definitions/propertyBag" + } + + }, + + "required": [ "tool" ] + }, + + "edge": { + "description": "Represents a directed edge in a graph.", + "type": "object", + "additionalProperties": false, + "properties": { + + "id": { + "description": "A string that uniquely identifies the edge within its graph.", + "type": "string" + }, + + "label": { + "description": "A short description of the edge.", + "$ref": "#/definitions/message" + }, + + "sourceNodeId": { + "description": "Identifies the source node (the node at which the edge starts).", + "type": "string" + }, + + "targetNodeId": { + "description": "Identifies the target node (the node at which the edge ends).", + "type": "string" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the edge.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "id", "sourceNodeId", "targetNodeId" ] + }, + + "edgeTraversal": { + "description": "Represents the traversal of a single edge during a graph traversal.", + "type": "object", + "additionalProperties": false, + "properties": { + + "edgeId": { + "description": "Identifies the edge being traversed.", + "type": "string" + }, + + "message": { + "description": "A message to display to the user as the edge is traversed.", + "$ref": "#/definitions/message" + }, + + "finalState": { + "description": "The values of relevant expressions after the edge has been traversed.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/multiformatMessageString" + } + }, + + "stepOverEdgeCount": { + "description": "The number of edge traversals necessary to return from a nested graph.", + "type": "integer", + "minimum": 0 + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the edge traversal.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "edgeId" ] + }, + + "exception": { + "description": "Describes a runtime exception encountered during the execution of an analysis tool.", + "type": "object", + "additionalProperties": false, + "properties": { + + "kind": { + "type": "string", + "description": "A string that identifies the kind of exception, for example, the fully qualified type name of an object that was thrown, or the symbolic name of a signal." + }, + + "message": { + "description": "A message that describes the exception.", + "type": "string" + }, + + "stack": { + "description": "The sequence of function calls leading to the exception.", + "$ref": "#/definitions/stack" + }, + + "innerExceptions": { + "description": "An array of exception objects each of which is considered a cause of this exception.", + "type": "array", + "minItems": 0, + "default": [], + "items": { + "$ref": "#/definitions/exception" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the exception.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "externalProperties": { + "description": "The top-level element of an external property file.", + "type": "object", + "additionalProperties": false, + "properties": { + + "schema": { + "description": "The URI of the JSON schema corresponding to the version of the external property file format.", + "type": "string", + "format": "uri" + }, + + "version": { + "description": "The SARIF format version of this external properties object.", + "enum": [ "2.1.0" ] + }, + + "guid": { + "description": "A stable, unique identifer for this external properties object, in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "runGuid": { + "description": "A stable, unique identifer for the run associated with this external properties object, in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "conversion": { + "description": "A conversion object that will be merged with a separate run.", + "$ref": "#/definitions/conversion" + }, + + "graphs": { + "description": "An array of graph objects that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "default": [], + "uniqueItems": true, + "items": { + "$ref": "#/definitions/graph" + } + }, + + "externalizedProperties": { + "description": "Key/value pairs that provide additional information that will be merged with a separate run.", + "$ref": "#/definitions/propertyBag" + }, + + "artifacts": { + "description": "An array of artifact objects that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/artifact" + } + }, + + "invocations": { + "description": "Describes the invocation of the analysis tool that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "default": [], + "items": { + "$ref": "#/definitions/invocation" + } + }, + + "logicalLocations": { + "description": "An array of logical locations such as namespaces, types or functions that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/logicalLocation" + } + }, + + "threadFlowLocations": { + "description": "An array of threadFlowLocation objects that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/threadFlowLocation" + } + }, + + "results": { + "description": "An array of result objects that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "default": [], + "items": { + "$ref": "#/definitions/result" + } + }, + + "taxonomies": { + "description": "Tool taxonomies that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponent" + } + }, + + "driver": { + "description": "The analysis tool object that will be merged with a separate run.", + "$ref": "#/definitions/toolComponent" + }, + + "extensions": { + "description": "Tool extensions that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponent" + } + }, + + "policies": { + "description": "Tool policies that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponent" + } + }, + + "translations": { + "description": "Tool translations that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponent" + } + }, + + "addresses": { + "description": "Addresses that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "default": [], + "items": { + "$ref": "#/definitions/address" + } + }, + + "webRequests": { + "description": "Requests that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/webRequest" + } + }, + + "webResponses": { + "description": "Responses that will be merged with a separate run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/webResponse" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the external properties.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "externalPropertyFileReference": { + "description": "Contains information that enables a SARIF consumer to locate the external property file that contains the value of an externalized property associated with the run.", + "type": "object", + "additionalProperties": false, + "properties": { + + "location": { + "description": "The location of the external property file.", + "$ref": "#/definitions/artifactLocation" + }, + + "guid": { + "description": "A stable, unique identifer for the external property file in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "itemCount": { + "description": "A non-negative integer specifying the number of items contained in the external property file.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the external property file.", + "$ref": "#/definitions/propertyBag" + } + }, + "anyOf": [ + { "required": [ "location" ] }, + { "required": [ "guid" ] } + ] + }, + + "externalPropertyFileReferences": { + "description": "References to external property files that should be inlined with the content of a root log file.", + "additionalProperties": false, + "type": "object", + "properties": { + + "conversion": { + "description": "An external property file containing a run.conversion object to be merged with the root log file.", + "$ref": "#/definitions/externalPropertyFileReference" + }, + + "graphs": { + "description": "An array of external property files containing a run.graphs object to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "externalizedProperties": { + "description": "An external property file containing a run.properties object to be merged with the root log file.", + "$ref": "#/definitions/externalPropertyFileReference" + }, + + "artifacts": { + "description": "An array of external property files containing run.artifacts arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "invocations": { + "description": "An array of external property files containing run.invocations arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "logicalLocations": { + "description": "An array of external property files containing run.logicalLocations arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "threadFlowLocations": { + "description": "An array of external property files containing run.threadFlowLocations arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "results": { + "description": "An array of external property files containing run.results arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "taxonomies": { + "description": "An array of external property files containing run.taxonomies arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "addresses": { + "description": "An array of external property files containing run.addresses arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "driver": { + "description": "An external property file containing a run.driver object to be merged with the root log file.", + "$ref": "#/definitions/externalPropertyFileReference" + }, + + "extensions": { + "description": "An array of external property files containing run.extensions arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "policies": { + "description": "An array of external property files containing run.policies arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "translations": { + "description": "An array of external property files containing run.translations arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "webRequests": { + "description": "An array of external property files containing run.requests arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "webResponses": { + "description": "An array of external property files containing run.responses arrays to be merged with the root log file.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/externalPropertyFileReference" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the external property files.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "fix": { + "description": "A proposed fix for the problem represented by a result object. A fix specifies a set of artifacts to modify. For each artifact, it specifies a set of bytes to remove, and provides a set of new bytes to replace them.", + "additionalProperties": false, + "type": "object", + "properties": { + + "description": { + "description": "A message that describes the proposed fix, enabling viewers to present the proposed change to an end user.", + "$ref": "#/definitions/message" + }, + + "artifactChanges": { + "description": "One or more artifact changes that comprise a fix for a result.", + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/artifactChange" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the fix.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "artifactChanges" ] + }, + + "graph": { + "description": "A network of nodes and directed edges that describes some aspect of the structure of the code (for example, a call graph).", + "type": "object", + "additionalProperties": false, + "properties": { + + "description": { + "description": "A description of the graph.", + "$ref": "#/definitions/message" + }, + + "nodes": { + "description": "An array of node objects representing the nodes of the graph.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/node" + } + }, + + "edges": { + "description": "An array of edge objects representing the edges of the graph.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/edge" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the graph.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "graphTraversal": { + "description": "Represents a path through a graph.", + "type": "object", + "additionalProperties": false, + "properties": { + + "runGraphIndex": { + "description": "The index within the run.graphs to be associated with the result.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "resultGraphIndex": { + "description": "The index within the result.graphs to be associated with the result.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "description": { + "description": "A description of this graph traversal.", + "$ref": "#/definitions/message" + }, + + "initialState": { + "description": "Values of relevant expressions at the start of the graph traversal that may change during graph traversal.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/multiformatMessageString" + } + }, + + "immutableState": { + "description": "Values of relevant expressions at the start of the graph traversal that remain constant for the graph traversal.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/multiformatMessageString" + } + }, + + "edgeTraversals": { + "description": "The sequences of edges traversed by this graph traversal.", + "type": "array", + "minItems": 0, + "default": [], + "items": { + "$ref": "#/definitions/edgeTraversal" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the graph traversal.", + "$ref": "#/definitions/propertyBag" + } + }, + "oneOf": [ + { "required": [ "runGraphIndex" ] }, + { "required": [ "resultGraphIndex" ] } + ] + }, + + "invocation": { + "description": "The runtime environment of the analysis tool run.", + "additionalProperties": false, + "type": "object", + "properties": { + + "commandLine": { + "description": "The command line used to invoke the tool.", + "type": "string" + }, + + "arguments": { + "description": "An array of strings, containing in order the command line arguments passed to the tool from the operating system.", + "type": "array", + "minItems": 0, + "items": { + "type": "string" + } + }, + + "responseFiles": { + "description": "The locations of any response files specified on the tool's command line.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/artifactLocation" + } + }, + + "startTimeUtc": { + "description": "The Coordinated Universal Time (UTC) date and time at which the run started. See \"Date/time properties\" in the SARIF spec for the required format.", + "type": "string", + "format": "date-time" + }, + + "endTimeUtc": { + "description": "The Coordinated Universal Time (UTC) date and time at which the run ended. See \"Date/time properties\" in the SARIF spec for the required format.", + "type": "string", + "format": "date-time" + }, + + "exitCode": { + "description": "The process exit code.", + "type": "integer" + }, + + "ruleConfigurationOverrides": { + "description": "An array of configurationOverride objects that describe rules related runtime overrides.", + "type": "array", + "minItems": 0, + "default": [], + "uniqueItems": true, + "items": { + "$ref": "#/definitions/configurationOverride" + } + }, + + "notificationConfigurationOverrides": { + "description": "An array of configurationOverride objects that describe notifications related runtime overrides.", + "type": "array", + "minItems": 0, + "default": [], + "uniqueItems": true, + "items": { + "$ref": "#/definitions/configurationOverride" + } + }, + + "toolExecutionNotifications": { + "description": "A list of runtime conditions detected by the tool during the analysis.", + "type": "array", + "minItems": 0, + "default": [], + "items": { + "$ref": "#/definitions/notification" + } + }, + + "toolConfigurationNotifications": { + "description": "A list of conditions detected by the tool that are relevant to the tool's configuration.", + "type": "array", + "minItems": 0, + "default": [], + "items": { + "$ref": "#/definitions/notification" + } + }, + + "exitCodeDescription": { + "description": "The reason for the process exit.", + "type": "string" + }, + + "exitSignalName": { + "description": "The name of the signal that caused the process to exit.", + "type": "string" + }, + + "exitSignalNumber": { + "description": "The numeric value of the signal that caused the process to exit.", + "type": "integer" + }, + + "processStartFailureMessage": { + "description": "The reason given by the operating system that the process failed to start.", + "type": "string" + }, + + "executionSuccessful": { + "description": "Specifies whether the tool's execution completed successfully.", + "type": "boolean" + }, + + "machine": { + "description": "The machine that hosted the analysis tool run.", + "type": "string" + }, + + "account": { + "description": "The account that ran the analysis tool.", + "type": "string" + }, + + "processId": { + "description": "The process id for the analysis tool run.", + "type": "integer" + }, + + "executableLocation": { + "description": "An absolute URI specifying the location of the analysis tool's executable.", + "$ref": "#/definitions/artifactLocation" + }, + + "workingDirectory": { + "description": "The working directory for the analysis tool run.", + "$ref": "#/definitions/artifactLocation" + }, + + "environmentVariables": { + "description": "The environment variables associated with the analysis tool process, expressed as key/value pairs.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + + "stdin": { + "description": "A file containing the standard input stream to the process that was invoked.", + "$ref": "#/definitions/artifactLocation" + }, + + "stdout": { + "description": "A file containing the standard output stream from the process that was invoked.", + "$ref": "#/definitions/artifactLocation" + }, + + "stderr": { + "description": "A file containing the standard error stream from the process that was invoked.", + "$ref": "#/definitions/artifactLocation" + }, + + "stdoutStderr": { + "description": "A file containing the interleaved standard output and standard error stream from the process that was invoked.", + "$ref": "#/definitions/artifactLocation" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the invocation.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "executionSuccessful" ] + }, + + "location": { + "description": "A location within a programming artifact.", + "additionalProperties": false, + "type": "object", + "properties": { + + "id": { + "description": "Value that distinguishes this location from all other locations within a single result object.", + "type": "integer", + "minimum": -1, + "default": -1 + }, + + "physicalLocation": { + "description": "Identifies the artifact and region.", + "$ref": "#/definitions/physicalLocation" + }, + + "logicalLocations": { + "description": "The logical locations associated with the result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/logicalLocation" + } + }, + + "message": { + "description": "A message relevant to the location.", + "$ref": "#/definitions/message" + }, + + "annotations": { + "description": "A set of regions relevant to the location.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/region" + } + }, + + "relationships": { + "description": "An array of objects that describe relationships between this location and others.", + "type": "array", + "default": [], + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/locationRelationship" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the location.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "locationRelationship": { + "description": "Information about the relation of one location to another.", + "type": "object", + "additionalProperties": false, + "properties": { + + "target": { + "description": "A reference to the related location.", + "type": "integer", + "minimum": 0 + }, + + "kinds": { + "description": "A set of distinct strings that categorize the relationship. Well-known kinds include 'includes', 'isIncludedBy' and 'relevant'.", + "type": "array", + "default": [ "relevant" ], + "uniqueItems": true, + "items": { + "type": "string" + } + }, + + "description": { + "description": "A description of the location relationship.", + "$ref": "#/definitions/message" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the location relationship.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "target" ] + }, + + "logicalLocation": { + "description": "A logical location of a construct that produced a result.", + "additionalProperties": false, + "type": "object", + "properties": { + + "name": { + "description": "Identifies the construct in which the result occurred. For example, this property might contain the name of a class or a method.", + "type": "string" + }, + + "index": { + "description": "The index within the logical locations array.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "fullyQualifiedName": { + "description": "The human-readable fully qualified name of the logical location.", + "type": "string" + }, + + "decoratedName": { + "description": "The machine-readable name for the logical location, such as a mangled function name provided by a C++ compiler that encodes calling convention, return type and other details along with the function name.", + "type": "string" + }, + + "parentIndex": { + "description": "Identifies the index of the immediate parent of the construct in which the result was detected. For example, this property might point to a logical location that represents the namespace that holds a type.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "kind": { + "description": "The type of construct this logical location component refers to. Should be one of 'function', 'member', 'module', 'namespace', 'parameter', 'resource', 'returnType', 'type', 'variable', 'object', 'array', 'property', 'value', 'element', 'text', 'attribute', 'comment', 'declaration', 'dtd' or 'processingInstruction', if any of those accurately describe the construct.", + "type": "string" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the logical location.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "message": { + "description": "Encapsulates a message intended to be read by the end user.", + "type": "object", + "additionalProperties": false, + + "properties": { + + "text": { + "description": "A plain text message string.", + "type": "string" + }, + + "markdown": { + "description": "A Markdown message string.", + "type": "string" + }, + + "id": { + "description": "The identifier for this message.", + "type": "string" + }, + + "arguments": { + "description": "An array of strings to substitute into the message string.", + "type": "array", + "minItems": 0, + "default": [], + "items": { + "type": "string" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the message.", + "$ref": "#/definitions/propertyBag" + } + }, + "anyOf": [ + { "required": [ "text" ] }, + { "required": [ "id" ] } + ] + }, + + "multiformatMessageString": { + "description": "A message string or message format string rendered in multiple formats.", + "type": "object", + "additionalProperties": false, + + "properties": { + + "text": { + "description": "A plain text message string or format string.", + "type": "string" + }, + + "markdown": { + "description": "A Markdown message string or format string.", + "type": "string" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the message.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "text" ] + }, + + "node": { + "description": "Represents a node in a graph.", + "type": "object", + "additionalProperties": false, + + "properties": { + + "id": { + "description": "A string that uniquely identifies the node within its graph.", + "type": "string" + }, + + "label": { + "description": "A short description of the node.", + "$ref": "#/definitions/message" + }, + + "location": { + "description": "A code location associated with the node.", + "$ref": "#/definitions/location" + }, + + "children": { + "description": "Array of child nodes.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/node" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the node.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "id" ] + }, + + "notification": { + "description": "Describes a condition relevant to the tool itself, as opposed to being relevant to a target being analyzed by the tool.", + "type": "object", + "additionalProperties": false, + "properties": { + + "locations": { + "description": "The locations relevant to this notification.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/location" + } + }, + + "message": { + "description": "A message that describes the condition that was encountered.", + "$ref": "#/definitions/message" + }, + + "level": { + "description": "A value specifying the severity level of the notification.", + "default": "warning", + "enum": [ "none", "note", "warning", "error" ] + }, + + "threadId": { + "description": "The thread identifier of the code that generated the notification.", + "type": "integer" + }, + + "timeUtc": { + "description": "The Coordinated Universal Time (UTC) date and time at which the analysis tool generated the notification.", + "type": "string", + "format": "date-time" + }, + + "exception": { + "description": "The runtime exception, if any, relevant to this notification.", + "$ref": "#/definitions/exception" + }, + + "descriptor": { + "description": "A reference used to locate the descriptor relevant to this notification.", + "$ref": "#/definitions/reportingDescriptorReference" + }, + + "associatedRule": { + "description": "A reference used to locate the rule descriptor associated with this notification.", + "$ref": "#/definitions/reportingDescriptorReference" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the notification.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "message" ] + }, + + "physicalLocation": { + "description": "A physical location relevant to a result. Specifies a reference to a programming artifact together with a range of bytes or characters within that artifact.", + "additionalProperties": false, + "type": "object", + "properties": { + + "address": { + "description": "The address of the location.", + "$ref": "#/definitions/address" + }, + + "artifactLocation": { + "description": "The location of the artifact.", + "$ref": "#/definitions/artifactLocation" + }, + + "region": { + "description": "Specifies a portion of the artifact.", + "$ref": "#/definitions/region" + }, + + "contextRegion": { + "description": "Specifies a portion of the artifact that encloses the region. Allows a viewer to display additional context around the region.", + "$ref": "#/definitions/region" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the physical location.", + "$ref": "#/definitions/propertyBag" + } + }, + + "anyOf": [ + { + "required": [ "address" ] + }, + { + "required": [ "artifactLocation" ] + } + ] + }, + + "propertyBag": { + "description": "Key/value pairs that provide additional information about the object.", + "type": "object", + "additionalProperties": true, + "properties": { + "tags": { + + "description": "A set of distinct strings that provide additional information.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "type": "string" + } + } + } + }, + + "rectangle": { + "description": "An area within an image.", + "additionalProperties": false, + "type": "object", + "properties": { + + "top": { + "description": "The Y coordinate of the top edge of the rectangle, measured in the image's natural units.", + "type": "number" + }, + + "left": { + "description": "The X coordinate of the left edge of the rectangle, measured in the image's natural units.", + "type": "number" + }, + + "bottom": { + "description": "The Y coordinate of the bottom edge of the rectangle, measured in the image's natural units.", + "type": "number" + }, + + "right": { + "description": "The X coordinate of the right edge of the rectangle, measured in the image's natural units.", + "type": "number" + }, + + "message": { + "description": "A message relevant to the rectangle.", + "$ref": "#/definitions/message" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the rectangle.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "region": { + "description": "A region within an artifact where a result was detected.", + "additionalProperties": false, + "type": "object", + "properties": { + + "startLine": { + "description": "The line number of the first character in the region.", + "type": "integer", + "minimum": 1 + }, + + "startColumn": { + "description": "The column number of the first character in the region.", + "type": "integer", + "minimum": 1 + }, + + "endLine": { + "description": "The line number of the last character in the region.", + "type": "integer", + "minimum": 1 + }, + + "endColumn": { + "description": "The column number of the character following the end of the region.", + "type": "integer", + "minimum": 1 + }, + + "charOffset": { + "description": "The zero-based offset from the beginning of the artifact of the first character in the region.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "charLength": { + "description": "The length of the region in characters.", + "type": "integer", + "minimum": 0 + }, + + "byteOffset": { + "description": "The zero-based offset from the beginning of the artifact of the first byte in the region.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "byteLength": { + "description": "The length of the region in bytes.", + "type": "integer", + "minimum": 0 + }, + + "snippet": { + "description": "The portion of the artifact contents within the specified region.", + "$ref": "#/definitions/artifactContent" + }, + + "message": { + "description": "A message relevant to the region.", + "$ref": "#/definitions/message" + }, + + "sourceLanguage": { + "description": "Specifies the source language, if any, of the portion of the artifact specified by the region object.", + "type": "string" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the region.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "replacement": { + "description": "The replacement of a single region of an artifact.", + "additionalProperties": false, + "type": "object", + "properties": { + + "deletedRegion": { + "description": "The region of the artifact to delete.", + "$ref": "#/definitions/region" + }, + + "insertedContent": { + "description": "The content to insert at the location specified by the 'deletedRegion' property.", + "$ref": "#/definitions/artifactContent" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the replacement.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "deletedRegion" ] + }, + + "reportingDescriptor": { + "description": "Metadata that describes a specific report produced by the tool, as part of the analysis it provides or its runtime reporting.", + "additionalProperties": false, + "type": "object", + "properties": { + + "id": { + "description": "A stable, opaque identifier for the report.", + "type": "string" + }, + + "deprecatedIds": { + "description": "An array of stable, opaque identifiers by which this report was known in some previous version of the analysis tool.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "type": "string" + } + }, + + "guid": { + "description": "A unique identifer for the reporting descriptor in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "deprecatedGuids": { + "description": "An array of unique identifies in the form of a GUID by which this report was known in some previous version of the analysis tool.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + } + }, + + "name": { + "description": "A report identifier that is understandable to an end user.", + "type": "string" + }, + + "deprecatedNames": { + "description": "An array of readable identifiers by which this report was known in some previous version of the analysis tool.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "type": "string" + } + }, + + "shortDescription": { + "description": "A concise description of the report. Should be a single sentence that is understandable when visible space is limited to a single line of text.", + "$ref": "#/definitions/multiformatMessageString" + }, + + "fullDescription": { + "description": "A description of the report. Should, as far as possible, provide details sufficient to enable resolution of any problem indicated by the result.", + "$ref": "#/definitions/multiformatMessageString" + }, + + "messageStrings": { + "description": "A set of name/value pairs with arbitrary names. Each value is a multiformatMessageString object, which holds message strings in plain text and (optionally) Markdown format. The strings can include placeholders, which can be used to construct a message in combination with an arbitrary number of additional string arguments.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/multiformatMessageString" + } + }, + + "defaultConfiguration": { + "description": "Default reporting configuration information.", + "$ref": "#/definitions/reportingConfiguration" + }, + + "helpUri": { + "description": "A URI where the primary documentation for the report can be found.", + "type": "string", + "format": "uri" + }, + + "help": { + "description": "Provides the primary documentation for the report, useful when there is no online documentation.", + "$ref": "#/definitions/multiformatMessageString" + }, + + "relationships": { + "description": "An array of objects that describe relationships between this reporting descriptor and others.", + "type": "array", + "default": [], + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/reportingDescriptorRelationship" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the report.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "id" ] + }, + + "reportingConfiguration": { + "description": "Information about a rule or notification that can be configured at runtime.", + "type": "object", + "additionalProperties": false, + "properties": { + + "enabled": { + "description": "Specifies whether the report may be produced during the scan.", + "type": "boolean", + "default": true + }, + + "level": { + "description": "Specifies the failure level for the report.", + "default": "warning", + "enum": [ "none", "note", "warning", "error" ] + }, + + "rank": { + "description": "Specifies the relative priority of the report. Used for analysis output only.", + "type": "number", + "default": -1.0, + "minimum": -1.0, + "maximum": 100.0 + }, + + "parameters": { + "description": "Contains configuration information specific to a report.", + "$ref": "#/definitions/propertyBag" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the reporting configuration.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "reportingDescriptorReference": { + "description": "Information about how to locate a relevant reporting descriptor.", + "type": "object", + "additionalProperties": false, + "properties": { + + "id": { + "description": "The id of the descriptor.", + "type": "string" + }, + + "index": { + "description": "The index into an array of descriptors in toolComponent.ruleDescriptors, toolComponent.notificationDescriptors, or toolComponent.taxonomyDescriptors, depending on context.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "guid": { + "description": "A guid that uniquely identifies the descriptor.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "toolComponent": { + "description": "A reference used to locate the toolComponent associated with the descriptor.", + "$ref": "#/definitions/toolComponentReference" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the reporting descriptor reference.", + "$ref": "#/definitions/propertyBag" + } + }, + "anyOf": [ + { "required": [ "index" ] }, + { "required": [ "guid" ] }, + { "required": [ "id" ] } + ] + }, + + "reportingDescriptorRelationship": { + "description": "Information about the relation of one reporting descriptor to another.", + "type": "object", + "additionalProperties": false, + "properties": { + + "target": { + "description": "A reference to the related reporting descriptor.", + "$ref": "#/definitions/reportingDescriptorReference" + }, + + "kinds": { + "description": "A set of distinct strings that categorize the relationship. Well-known kinds include 'canPrecede', 'canFollow', 'willPrecede', 'willFollow', 'superset', 'subset', 'equal', 'disjoint', 'relevant', and 'incomparable'.", + "type": "array", + "default": [ "relevant" ], + "uniqueItems": true, + "items": { + "type": "string" + } + }, + + "description": { + "description": "A description of the reporting descriptor relationship.", + "$ref": "#/definitions/message" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the reporting descriptor reference.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "target" ] + }, + + "result": { + "description": "A result produced by an analysis tool.", + "additionalProperties": false, + "type": "object", + "properties": { + + "ruleId": { + "description": "The stable, unique identifier of the rule, if any, to which this notification is relevant. This member can be used to retrieve rule metadata from the rules dictionary, if it exists.", + "type": "string" + }, + + "ruleIndex": { + "description": "The index within the tool component rules array of the rule object associated with this result.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "rule": { + "description": "A reference used to locate the rule descriptor relevant to this result.", + "$ref": "#/definitions/reportingDescriptorReference" + }, + + "kind": { + "description": "A value that categorizes results by evaluation state.", + "default": "fail", + "enum": [ "notApplicable", "pass", "fail", "review", "open", "informational" ] + }, + + "level": { + "description": "A value specifying the severity level of the result.", + "default": "warning", + "enum": [ "none", "note", "warning", "error" ] + }, + + "message": { + "description": "A message that describes the result. The first sentence of the message only will be displayed when visible space is limited.", + "$ref": "#/definitions/message" + }, + + "analysisTarget": { + "description": "Identifies the artifact that the analysis tool was instructed to scan. This need not be the same as the artifact where the result actually occurred.", + "$ref": "#/definitions/artifactLocation" + }, + + "locations": { + "description": "The set of locations where the result was detected. Specify only one location unless the problem indicated by the result can only be corrected by making a change at every specified location.", + "type": "array", + "minItems": 0, + "default": [], + "items": { + "$ref": "#/definitions/location" + } + }, + + "guid": { + "description": "A stable, unique identifer for the result in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "correlationGuid": { + "description": "A stable, unique identifier for the equivalence class of logically identical results to which this result belongs, in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "occurrenceCount": { + "description": "A positive integer specifying the number of times this logically unique result was observed in this run.", + "type": "integer", + "minimum": 1 + }, + + "partialFingerprints": { + "description": "A set of strings that contribute to the stable, unique identity of the result.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + + "fingerprints": { + "description": "A set of strings each of which individually defines a stable, unique identity for the result.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + + "stacks": { + "description": "An array of 'stack' objects relevant to the result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/stack" + } + }, + + "codeFlows": { + "description": "An array of 'codeFlow' objects relevant to the result.", + "type": "array", + "minItems": 0, + "default": [], + "items": { + "$ref": "#/definitions/codeFlow" + } + }, + + "graphs": { + "description": "An array of zero or more unique graph objects associated with the result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/graph" + } + }, + + "graphTraversals": { + "description": "An array of one or more unique 'graphTraversal' objects.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/graphTraversal" + } + }, + + "relatedLocations": { + "description": "A set of locations relevant to this result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/location" + } + }, + + "suppressions": { + "description": "A set of suppressions relevant to this result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/suppression" + } + }, + + "baselineState": { + "description": "The state of a result relative to a baseline of a previous run.", + "enum": [ + "new", + "unchanged", + "updated", + "absent" + ] + }, + + "rank": { + "description": "A number representing the priority or importance of the result.", + "type": "number", + "default": -1.0, + "minimum": -1.0, + "maximum": 100.0 + }, + + "attachments": { + "description": "A set of artifacts relevant to the result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/attachment" + } + }, + + "hostedViewerUri": { + "description": "An absolute URI at which the result can be viewed.", + "type": "string", + "format": "uri" + }, + + "workItemUris": { + "description": "The URIs of the work items associated with this result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "type": "string", + "format": "uri" + } + }, + + "provenance": { + "description": "Information about how and when the result was detected.", + "$ref": "#/definitions/resultProvenance" + }, + + "fixes": { + "description": "An array of 'fix' objects, each of which represents a proposed fix to the problem indicated by the result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/fix" + } + }, + + "taxa": { + "description": "An array of references to taxonomy reporting descriptors that are applicable to the result.", + "type": "array", + "default": [], + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/reportingDescriptorReference" + } + }, + + "webRequest": { + "description": "A web request associated with this result.", + "$ref": "#/definitions/webRequest" + }, + + "webResponse": { + "description": "A web response associated with this result.", + "$ref": "#/definitions/webResponse" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the result.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "message" ] + }, + + "resultProvenance": { + "description": "Contains information about how and when a result was detected.", + "additionalProperties": false, + "type": "object", + "properties": { + + "firstDetectionTimeUtc": { + "description": "The Coordinated Universal Time (UTC) date and time at which the result was first detected. See \"Date/time properties\" in the SARIF spec for the required format.", + "type": "string", + "format": "date-time" + }, + + "lastDetectionTimeUtc": { + "description": "The Coordinated Universal Time (UTC) date and time at which the result was most recently detected. See \"Date/time properties\" in the SARIF spec for the required format.", + "type": "string", + "format": "date-time" + }, + + "firstDetectionRunGuid": { + "description": "A GUID-valued string equal to the automationDetails.guid property of the run in which the result was first detected.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "lastDetectionRunGuid": { + "description": "A GUID-valued string equal to the automationDetails.guid property of the run in which the result was most recently detected.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "invocationIndex": { + "description": "The index within the run.invocations array of the invocation object which describes the tool invocation that detected the result.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "conversionSources": { + "description": "An array of physicalLocation objects which specify the portions of an analysis tool's output that a converter transformed into the result.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/physicalLocation" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the result.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "run": { + "description": "Describes a single run of an analysis tool, and contains the reported output of that run.", + "additionalProperties": false, + "type": "object", + "properties": { + + "tool": { + "description": "Information about the tool or tool pipeline that generated the results in this run. A run can only contain results produced by a single tool or tool pipeline. A run can aggregate results from multiple log files, as long as context around the tool run (tool command-line arguments and the like) is identical for all aggregated files.", + "$ref": "#/definitions/tool" + }, + + "invocations": { + "description": "Describes the invocation of the analysis tool.", + "type": "array", + "minItems": 0, + "default": [], + "items": { + "$ref": "#/definitions/invocation" + } + }, + + "conversion": { + "description": "A conversion object that describes how a converter transformed an analysis tool's native reporting format into the SARIF format.", + "$ref": "#/definitions/conversion" + }, + + "language": { + "description": "The language of the messages emitted into the log file during this run (expressed as an ISO 639-1 two-letter lowercase culture code) and an optional region (expressed as an ISO 3166-1 two-letter uppercase subculture code associated with a country or region). The casing is recommended but not required (in order for this data to conform to RFC5646).", + "type": "string", + "default": "en-US", + "pattern": "^[a-zA-Z]{2}(-[a-zA-Z]{2})?$" + }, + "versionControlProvenance": { + "description": "Specifies the revision in version control of the artifacts that were scanned.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/versionControlDetails" + } + }, + + "originalUriBaseIds": { + "description": "The artifact location specified by each uriBaseId symbol on the machine where the tool originally ran.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/artifactLocation" + } + }, + + "artifacts": { + "description": "An array of artifact objects relevant to the run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/artifact" + } + }, + + "logicalLocations": { + "description": "An array of logical locations such as namespaces, types or functions.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/logicalLocation" + } + }, + + "graphs": { + "description": "An array of zero or more unique graph objects associated with the run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/graph" + } + }, + + "results": { + "description": "The set of results contained in an SARIF log. The results array can be omitted when a run is solely exporting rules metadata. It must be present (but may be empty) if a log file represents an actual scan.", + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/definitions/result" + } + }, + + "automationDetails": { + "description": "Automation details that describe this run.", + "$ref": "#/definitions/runAutomationDetails" + }, + + "runAggregates": { + "description": "Automation details that describe the aggregate of runs to which this run belongs.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/runAutomationDetails" + } + }, + + "baselineGuid": { + "description": "The 'guid' property of a previous SARIF 'run' that comprises the baseline that was used to compute result 'baselineState' properties for the run.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "redactionTokens": { + "description": "An array of strings used to replace sensitive information in a redaction-aware property.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "type": "string" + } + }, + + "defaultEncoding": { + "description": "Specifies the default encoding for any artifact object that refers to a text file.", + "type": "string" + }, + + "defaultSourceLanguage": { + "description": "Specifies the default source language for any artifact object that refers to a text file that contains source code.", + "type": "string" + }, + + "newlineSequences": { + "description": "An ordered list of character sequences that were treated as line breaks when computing region information for the run.", + "type": "array", + "minItems": 1, + "uniqueItems": true, + "default": [ "\r\n", "\n" ], + "items": { + "type": "string" + } + }, + + "columnKind": { + "description": "Specifies the unit in which the tool measures columns.", + "enum": [ "utf16CodeUnits", "unicodeCodePoints" ] + }, + + "externalPropertyFileReferences": { + "description": "References to external property files that should be inlined with the content of a root log file.", + "$ref": "#/definitions/externalPropertyFileReferences" + }, + + "threadFlowLocations": { + "description": "An array of threadFlowLocation objects cached at run level.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/threadFlowLocation" + } + }, + + "taxonomies": { + "description": "An array of toolComponent objects relevant to a taxonomy in which results are categorized.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponent" + } + }, + + "addresses": { + "description": "Addresses associated with this run instance, if any.", + "type": "array", + "minItems": 0, + "default": [], + "items": { + "$ref": "#/definitions/address" + } + }, + + "translations": { + "description": "The set of available translations of the localized data provided by the tool.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponent" + } + }, + + "policies": { + "description": "Contains configurations that may potentially override both reportingDescriptor.defaultConfiguration (the tool's default severities) and invocation.configurationOverrides (severities established at run-time from the command line).", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponent" + } + }, + + "webRequests": { + "description": "An array of request objects cached at run level.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/webRequest" + } + }, + + "webResponses": { + "description": "An array of response objects cached at run level.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/webResponse" + } + }, + + "specialLocations": { + "description": "A specialLocations object that defines locations of special significance to SARIF consumers.", + "$ref": "#/definitions/specialLocations" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the run.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "tool" ] + }, + + "runAutomationDetails": { + "description": "Information that describes a run's identity and role within an engineering system process.", + "additionalProperties": false, + "type": "object", + "properties": { + + "description": { + "description": "A description of the identity and role played within the engineering system by this object's containing run object.", + "$ref": "#/definitions/message" + }, + + "id": { + "description": "A hierarchical string that uniquely identifies this object's containing run object.", + "type": "string" + }, + + "guid": { + "description": "A stable, unique identifer for this object's containing run object in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "correlationGuid": { + "description": "A stable, unique identifier for the equivalence class of runs to which this object's containing run object belongs in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the run automation details.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "specialLocations": { + "description": "Defines locations of special significance to SARIF consumers.", + "type": "object", + "additionalProperties": false, + "properties": { + + "displayBase": { + "description": "Provides a suggestion to SARIF consumers to display file paths relative to the specified location.", + "$ref": "#/definitions/artifactLocation" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the special locations.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "stack": { + "description": "A call stack that is relevant to a result.", + "additionalProperties": false, + "type": "object", + "properties": { + + "message": { + "description": "A message relevant to this call stack.", + "$ref": "#/definitions/message" + }, + + "frames": { + "description": "An array of stack frames that represents a sequence of calls, rendered in reverse chronological order, that comprise the call stack.", + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/definitions/stackFrame" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the stack.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "frames" ] + }, + + "stackFrame": { + "description": "A function call within a stack trace.", + "additionalProperties": false, + "type": "object", + "properties": { + + "location": { + "description": "The location to which this stack frame refers.", + "$ref": "#/definitions/location" + }, + + "module": { + "description": "The name of the module that contains the code of this stack frame.", + "type": "string" + }, + + "threadId": { + "description": "The thread identifier of the stack frame.", + "type": "integer" + }, + + "parameters": { + "description": "The parameters of the call that is executing.", + "type": "array", + "minItems": 0, + "default": [], + "items": { + "type": "string", + "default": [] + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the stack frame.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "suppression": { + "description": "A suppression that is relevant to a result.", + "additionalProperties": false, + "type": "object", + "properties": { + + "guid": { + "description": "A stable, unique identifer for the suprression in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "kind": { + "description": "A string that indicates where the suppression is persisted.", + "enum": [ + "inSource", + "external" + ] + }, + + "state": { + "description": "A string that indicates the state of the suppression.", + "enum": [ + "accepted", + "underReview", + "rejected" + ] + }, + + "justification": { + "description": "A string representing the justification for the suppression.", + "type": "string" + }, + + "location": { + "description": "Identifies the location associated with the suppression.", + "$ref": "#/definitions/location" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the suppression.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "kind" ] + }, + + "threadFlow": { + "description": "Describes a sequence of code locations that specify a path through a single thread of execution such as an operating system or fiber.", + "type": "object", + "additionalProperties": false, + "properties": { + + "id": { + "description": "An string that uniquely identifies the threadFlow within the codeFlow in which it occurs.", + "type": "string" + }, + + "message": { + "description": "A message relevant to the thread flow.", + "$ref": "#/definitions/message" + }, + + + "initialState": { + "description": "Values of relevant expressions at the start of the thread flow that may change during thread flow execution.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/multiformatMessageString" + } + }, + + "immutableState": { + "description": "Values of relevant expressions at the start of the thread flow that remain constant.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/multiformatMessageString" + } + }, + + "locations": { + "description": "A temporally ordered array of 'threadFlowLocation' objects, each of which describes a location visited by the tool while producing the result.", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/threadFlowLocation" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the thread flow.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "locations" ] + }, + + "threadFlowLocation": { + "description": "A location visited by an analysis tool while simulating or monitoring the execution of a program.", + "additionalProperties": false, + "type": "object", + "properties": { + + "index": { + "description": "The index within the run threadFlowLocations array.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "location": { + "description": "The code location.", + "$ref": "#/definitions/location" + }, + + "stack": { + "description": "The call stack leading to this location.", + "$ref": "#/definitions/stack" + }, + + "kinds": { + "description": "A set of distinct strings that categorize the thread flow location. Well-known kinds include 'acquire', 'release', 'enter', 'exit', 'call', 'return', 'branch', 'implicit', 'false', 'true', 'caution', 'danger', 'unknown', 'unreachable', 'taint', 'function', 'handler', 'lock', 'memory', 'resource', 'scope' and 'value'.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "type": "string" + } + }, + + "taxa": { + "description": "An array of references to rule or taxonomy reporting descriptors that are applicable to the thread flow location.", + "type": "array", + "default": [], + "minItems": 0, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/reportingDescriptorReference" + } + }, + + "module": { + "description": "The name of the module that contains the code that is executing.", + "type": "string" + }, + + "state": { + "description": "A dictionary, each of whose keys specifies a variable or expression, the associated value of which represents the variable or expression value. For an annotation of kind 'continuation', for example, this dictionary might hold the current assumed values of a set of global variables.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/multiformatMessageString" + } + }, + + "nestingLevel": { + "description": "An integer representing a containment hierarchy within the thread flow.", + "type": "integer", + "minimum": 0 + }, + + "executionOrder": { + "description": "An integer representing the temporal order in which execution reached this location.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "executionTimeUtc": { + "description": "The Coordinated Universal Time (UTC) date and time at which this location was executed.", + "type": "string", + "format": "date-time" + }, + + "importance": { + "description": "Specifies the importance of this location in understanding the code flow in which it occurs. The order from most to least important is \"essential\", \"important\", \"unimportant\". Default: \"important\".", + "enum": [ "important", "essential", "unimportant" ], + "default": "important" + }, + + "webRequest": { + "description": "A web request associated with this thread flow location.", + "$ref": "#/definitions/webRequest" + }, + + "webResponse": { + "description": "A web response associated with this thread flow location.", + "$ref": "#/definitions/webResponse" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the threadflow location.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "tool": { + "description": "The analysis tool that was run.", + "additionalProperties": false, + "type": "object", + "properties": { + + "driver": { + "description": "The analysis tool that was run.", + "$ref": "#/definitions/toolComponent" + }, + + "extensions": { + "description": "Tool extensions that contributed to or reconfigured the analysis tool that was run.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponent" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the tool.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "driver" ] + }, + + "toolComponent": { + "description": "A component, such as a plug-in or the driver, of the analysis tool that was run.", + "additionalProperties": false, + "type": "object", + "properties": { + + "guid": { + "description": "A unique identifer for the tool component in the form of a GUID.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "name": { + "description": "The name of the tool component.", + "type": "string" + }, + + "organization": { + "description": "The organization or company that produced the tool component.", + "type": "string" + }, + + "product": { + "description": "A product suite to which the tool component belongs.", + "type": "string" + }, + + "productSuite": { + "description": "A localizable string containing the name of the suite of products to which the tool component belongs.", + "type": "string" + }, + + "shortDescription": { + "description": "A brief description of the tool component.", + "$ref": "#/definitions/multiformatMessageString" + }, + + "fullDescription": { + "description": "A comprehensive description of the tool component.", + "$ref": "#/definitions/multiformatMessageString" + }, + + "fullName": { + "description": "The name of the tool component along with its version and any other useful identifying information, such as its locale.", + "type": "string" + }, + + "version": { + "description": "The tool component version, in whatever format the component natively provides.", + "type": "string" + }, + + "semanticVersion": { + "description": "The tool component version in the format specified by Semantic Versioning 2.0.", + "type": "string" + }, + + "dottedQuadFileVersion": { + "description": "The binary version of the tool component's primary executable file expressed as four non-negative integers separated by a period (for operating systems that express file versions in this way).", + "type": "string", + "pattern": "[0-9]+(\\.[0-9]+){3}" + }, + + "releaseDateUtc": { + "description": "A string specifying the UTC date (and optionally, the time) of the component's release.", + "type": "string" + }, + + "downloadUri": { + "description": "The absolute URI from which the tool component can be downloaded.", + "type": "string", + "format": "uri" + }, + + "informationUri": { + "description": "The absolute URI at which information about this version of the tool component can be found.", + "type": "string", + "format": "uri" + }, + + "globalMessageStrings": { + "description": "A dictionary, each of whose keys is a resource identifier and each of whose values is a multiformatMessageString object, which holds message strings in plain text and (optionally) Markdown format. The strings can include placeholders, which can be used to construct a message in combination with an arbitrary number of additional string arguments.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/multiformatMessageString" + } + }, + + "notifications": { + "description": "An array of reportingDescriptor objects relevant to the notifications related to the configuration and runtime execution of the tool component.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/reportingDescriptor" + } + }, + + "rules": { + "description": "An array of reportingDescriptor objects relevant to the analysis performed by the tool component.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/reportingDescriptor" + } + }, + + "taxa": { + "description": "An array of reportingDescriptor objects relevant to the definitions of both standalone and tool-defined taxonomies.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/reportingDescriptor" + } + }, + + "locations": { + "description": "An array of the artifactLocation objects associated with the tool component.", + "type": "array", + "minItems": 0, + "default": [], + "items": { + "$ref": "#/definitions/artifactLocation" + } + }, + + "language": { + "description": "The language of the messages emitted into the log file during this run (expressed as an ISO 639-1 two-letter lowercase language code) and an optional region (expressed as an ISO 3166-1 two-letter uppercase subculture code associated with a country or region). The casing is recommended but not required (in order for this data to conform to RFC5646).", + "type": "string", + "default": "en-US", + "pattern": "^[a-zA-Z]{2}(-[a-zA-Z]{2})?$" + }, + + "contents": { + "description": "The kinds of data contained in this object.", + "type": "array", + "uniqueItems": true, + "default": [ "localizedData", "nonLocalizedData" ], + "items": { + "enum": [ + "localizedData", + "nonLocalizedData" + ] + } + }, + + "isComprehensive": { + "description": "Specifies whether this object contains a complete definition of the localizable and/or non-localizable data for this component, as opposed to including only data that is relevant to the results persisted to this log file.", + "type": "boolean", + "default": false + }, + + "localizedDataSemanticVersion": { + "description": "The semantic version of the localized strings defined in this component; maintained by components that provide translations.", + "type": "string" + }, + + "minimumRequiredLocalizedDataSemanticVersion": { + "description": "The minimum value of localizedDataSemanticVersion required in translations consumed by this component; used by components that consume translations.", + "type": "string" + }, + + "associatedComponent": { + "description": "The component which is strongly associated with this component. For a translation, this refers to the component which has been translated. For an extension, this is the driver that provides the extension's plugin model.", + "$ref": "#/definitions/toolComponentReference" + }, + + "translationMetadata": { + "description": "Translation metadata, required for a translation, not populated by other component types.", + "$ref": "#/definitions/translationMetadata" + }, + + "supportedTaxonomies": { + "description": "An array of toolComponentReference objects to declare the taxonomies supported by the tool component.", + "type": "array", + "minItems": 0, + "uniqueItems": true, + "default": [], + "items": { + "$ref": "#/definitions/toolComponentReference" + } + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the tool component.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "name" ] + }, + + "toolComponentReference": { + "description": "Identifies a particular toolComponent object, either the driver or an extension.", + "type": "object", + "additionalProperties": false, + "properties": { + + "name": { + "description": "The 'name' property of the referenced toolComponent.", + "type": "string" + }, + + "index": { + "description": "An index into the referenced toolComponent in tool.extensions.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "guid": { + "description": "The 'guid' property of the referenced toolComponent.", + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the toolComponentReference.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "translationMetadata": { + "description": "Provides additional metadata related to translation.", + "type": "object", + "additionalProperties": false, + "properties": { + + "name": { + "description": "The name associated with the translation metadata.", + "type": "string" + }, + + "fullName": { + "description": "The full name associated with the translation metadata.", + "type": "string" + }, + + "shortDescription": { + "description": "A brief description of the translation metadata.", + "$ref": "#/definitions/multiformatMessageString" + }, + + "fullDescription": { + "description": "A comprehensive description of the translation metadata.", + "$ref": "#/definitions/multiformatMessageString" + }, + + "downloadUri": { + "description": "The absolute URI from which the translation metadata can be downloaded.", + "type": "string", + "format": "uri" + }, + + "informationUri": { + "description": "The absolute URI from which information related to the translation metadata can be downloaded.", + "type": "string", + "format": "uri" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the translation metadata.", + "$ref": "#/definitions/propertyBag" + } + }, + "required": [ "name" ] + }, + + "versionControlDetails": { + "description": "Specifies the information necessary to retrieve a desired revision from a version control system.", + "type": "object", + "additionalProperties": false, + "properties": { + + "repositoryUri": { + "description": "The absolute URI of the repository.", + "type": "string", + "format": "uri" + }, + + "revisionId": { + "description": "A string that uniquely and permanently identifies the revision within the repository.", + "type": "string" + }, + + "branch": { + "description": "The name of a branch containing the revision.", + "type": "string" + }, + + "revisionTag": { + "description": "A tag that has been applied to the revision.", + "type": "string" + }, + + "asOfTimeUtc": { + "description": "A Coordinated Universal Time (UTC) date and time that can be used to synchronize an enlistment to the state of the repository at that time.", + "type": "string", + "format": "date-time" + }, + + "mappedTo": { + "description": "The location in the local file system to which the root of the repository was mapped at the time of the analysis.", + "$ref": "#/definitions/artifactLocation" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the version control details.", + "$ref": "#/definitions/propertyBag" + } + }, + + "required": [ "repositoryUri" ] + }, + + "webRequest": { + "description": "Describes an HTTP request.", + "type": "object", + "additionalProperties": false, + "properties": { + + "index": { + "description": "The index within the run.webRequests array of the request object associated with this result.", + "type": "integer", + "default": -1, + "minimum": -1 + + }, + + "protocol": { + "description": "The request protocol. Example: 'http'.", + "type": "string" + }, + + "version": { + "description": "The request version. Example: '1.1'.", + "type": "string" + }, + + "target": { + "description": "The target of the request.", + "type": "string" + }, + + "method": { + "description": "The HTTP method. Well-known values are 'GET', 'PUT', 'POST', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT'.", + "type": "string" + }, + + "headers": { + "description": "The request headers.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + + "parameters": { + "description": "The request parameters.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + + "body": { + "description": "The body of the request.", + "$ref": "#/definitions/artifactContent" + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the request.", + "$ref": "#/definitions/propertyBag" + } + } + }, + + "webResponse": { + "description": "Describes the response to an HTTP request.", + "type": "object", + "additionalProperties": false, + "properties": { + + "index": { + "description": "The index within the run.webResponses array of the response object associated with this result.", + "type": "integer", + "default": -1, + "minimum": -1 + }, + + "protocol": { + "description": "The response protocol. Example: 'http'.", + "type": "string" + }, + + "version": { + "description": "The response version. Example: '1.1'.", + "type": "string" + }, + + "statusCode": { + "description": "The response status code. Example: 451.", + "type": "integer" + }, + + "reasonPhrase": { + "description": "The response reason. Example: 'Not found'.", + "type": "string" + }, + + "headers": { + "description": "The response headers.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + + "body": { + "description": "The body of the response.", + "$ref": "#/definitions/artifactContent" + }, + + "noResponseReceived": { + "description": "Specifies whether a response was received from the server.", + "type": "boolean", + "default": false + }, + + "properties": { + "description": "Key/value pairs that provide additional information about the response.", + "$ref": "#/definitions/propertyBag" + } + } + } + } +} diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go index fb5c4bbb..5e8b5928 100644 --- a/internal/sarif/sarif.go +++ b/internal/sarif/sarif.go @@ -1,7 +1,12 @@ package sarif import ( + "bytes" + "context" + "encoding/json" "fmt" + "io" + "io/ioutil" "strings" "github.com/Shopify/kubeaudit" @@ -19,6 +24,7 @@ import ( "github.com/Shopify/kubeaudit/auditors/rootfs" "github.com/Shopify/kubeaudit/auditors/seccomp" "github.com/owenrumney/go-sarif/v2/sarif" + "github.com/qri-io/jsonschema" ) var Auditors = map[string]string{ @@ -79,7 +85,7 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { "kubernetes", "infrastructure", }, - "precision": "very-high", + "precision": "very-high", // TODO: can we remove this? }) // SARIF specifies the following severity levels: warning, error, note and none @@ -99,5 +105,46 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { run.AddResult(result) } + var reportBytes bytes.Buffer + + err = report.Write(&reportBytes) + if err != nil { + return nil, nil + } + + err, errs := Validate(&reportBytes) + + if err != nil { + return nil, fmt.Errorf("error validating SARIF schema: %s. %s", err, errs) + } + return report, nil } + +func Validate(report io.Reader) (error, []jsonschema.KeyError) { + schemaData, err := ioutil.ReadFile("sarif-schema.json") + if err != nil { + return err, nil + } + + jsonSchema := &jsonschema.Schema{} + + if err := json.Unmarshal(schemaData, jsonSchema); err != nil { + return err, nil + } + + _, ok := report.(*bytes.Buffer) + + if ok { + errs, err := jsonSchema.ValidateBytes(context.Background(), report.(*bytes.Buffer).Bytes()) + if err != nil { + return err, nil + } + + if len(errs) > 0 { + return nil, errs + } + } + + return nil, nil +} diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index e08a94e6..7e8ad88b 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -1,6 +1,9 @@ package sarif import ( + "bytes" + "fmt" + "io/ioutil" "os" "path/filepath" "testing" @@ -80,11 +83,47 @@ func TestCreate(t *testing.T) { var ruleNames []string // check for rules occurrences for _, sarifRule := range sarifReport.Runs[0].Tool.Driver.Rules { + assert.NotEqual(t, *sarifRule.Help.Markdown, "") + + assert.Equal(t, sarifRule.Properties["tags"], []string{ + "security", + "kubernetes", + "infrastructure", + }) + ruleNames = append(ruleNames, sarifRule.ID) } for _, expectedRule := range tc.expectedRules { assert.Contains(t, ruleNames, expectedRule) } + + // for _, sarifResult := range sarifReport.Runs[0].Results { + // // TODO: test for severity, message and location + // // assert.Equal(sarifResult.Level, + // } + + // also add a fixture with info level so that we capture the conversion to note + } } + +// Validates that the given file path refers to a valid SARIF file. +// Throws an error if the file is invalid. + +func TestValidate(t *testing.T) { + var reportBytes bytes.Buffer + testSarif, err := ioutil.ReadFile("fixtures/valid.sarif") + require.NoError(t, err) + + reportBytes.Write(testSarif) + + err, errs := Validate(&reportBytes) + require.NoError(t, err) + if len(errs) > 0 { + fmt.Println(errs) + } + + assert.Len(t, errs, 0) + +} From b7ae4eba643d3402b7486f0b55d22a146c228a9f Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Tue, 19 Jul 2022 11:03:22 -0400 Subject: [PATCH 10/31] try json validation --- internal/sarif/fixtures/valid.sarif | 1578 +++++++++++++++++++++++---- internal/sarif/sarif-schema.json | 25 +- internal/sarif/sarif.go | 22 +- internal/sarif/sarif_test.go | 7 +- 4 files changed, 1398 insertions(+), 234 deletions(-) diff --git a/internal/sarif/fixtures/valid.sarif b/internal/sarif/fixtures/valid.sarif index 03f2dc42..6ead9064 100644 --- a/internal/sarif/fixtures/valid.sarif +++ b/internal/sarif/fixtures/valid.sarif @@ -1,239 +1,1373 @@ { - "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", "version": "2.1.0", - "runs": [{ + "$schema": "https://json.schemastore.org/sarif-2.1.0-rtm.5.json", + "runs": [ + { "tool": { "driver": { - "name": "LGTM.com", - "organization": "Semmle", - "version": "1.24.0-SNAPSHOT", - "rules": [{ - "id": "js/unused-local-variable", - "name": "js/unused-local-variable", - "shortDescription": { - "text": "Unused variable, import, function or class" - }, - "fullDescription": { - "text": "Unused variables, imports, functions or classes may be a symptom of a bug and should be examined carefully." - }, - "defaultConfiguration": { - "level": "note" - }, - "properties": { - "tags": ["maintainability"], - "kind": "problem", - "precision": "very-high", - "name": "Unused variable, import, function or class", - "description": "Unused variables, imports, functions or classes may be a symptom of a bug\n and should be examined carefully.", - "id": "js/unused-local-variable", - "problem.severity": "recommendation" - } - }] - } - }, - "results": [{ - "ruleId": "js/unused-local-variable", - "ruleIndex": 0, - "message": { - "text": "Unused variable foo." - }, - "locations": [{ - "physicalLocation": { - "artifactLocation": { - "uri": "main.js", - "uriBaseId": "%SRCROOT%", - "index": 0 - }, - "region": { - "startLine": 2, - "startColumn": 7, - "endColumn": 10 - } - } - }], - "partialFingerprints": { - "primaryLocationLineHash": "39fa2ee980eb94b0:1", - "primaryLocationStartColumnFingerprint": "4" - } - }], - "columnKind": "utf16CodeUnits", - "properties": { - "semmle.formatSpecifier": "2.1.0", - "semmle.sourceLanguage": "java" - } - }, - { - "tool" : { - "driver" : { - "name" : "CodeQL command-line toolchain", - "organization" : "GitHub", - "semanticVersion" : "2.0.0", - "rules" : [ { - "id" : "js/unused-local-variable", - "name" : "js/unused-local-variable", - "shortDescription" : { - "text" : "Unused variable, import, function or class" - }, - "fullDescription" : { - "text" : "Unused variables, imports, functions or classes may be a symptom of a bug and should be examined carefully." - }, - "defaultConfiguration" : { - "level": "note" - }, - "properties" : { - "tags" : [ "maintainability" ], - "kind" : "problem", - "precision" : "very-high", - "name" : "Unused variable, import, function or class", - "description" : "Unused variables, imports, functions or classes may be a symptom of a bug\n and should be examined carefully.", - "id" : "js/unused-local-variable", - "problem.severity" : "recommendation" - } - }, - { - "id": "js/inconsistent-use-of-new", - "name": "js/inconsistent-use-of-new", - "shortDescription": { - "text": "Inconsistent use of 'new'" + "fullName": "Trivy Vulnerability Scanner", + "informationUri": "https://github.com/aquasecurity/trivy", + "name": "Trivy", + "rules": [ + { + "id": "KSV008", + "name": "Misconfiguration", + "shortDescription": { + "text": "KSV008" + }, + "fullDescription": { + "text": "Sharing the host’s IPC namespace allows container processes to communicate with processes on the host." + }, + "defaultConfiguration": { + "level": "error" + }, + "helpUri": "https://avd.aquasec.com/appshield/ksv008", + "help": { + "text": "Misconfiguration KSV008\nType: Kubernetes Security Check\nSeverity: HIGH\nCheck: Access to host IPC namespace\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)\nSharing the host’s IPC namespace allows container processes to communicate with processes on the host.", + "markdown": "**Misconfiguration KSV008**\n| Type | Severity | Check | Message | Link |\n| --- | --- | --- | --- | --- |\n|Kubernetes Security Check|HIGH|Access to host IPC namespace|StatefulSet 'statefulset' should not set 'spec.template.spec.hostIPC' to true|[KSV008](https://avd.aquasec.com/appshield/ksv008)|\n\nSharing the host’s IPC namespace allows container processes to communicate with processes on the host." + }, + "properties": { + "precision": "very-high", + "security-severity": "8.0", + "tags": [ + "misconfiguration", + "security", + "HIGH" + ] + } }, - "fullDescription": { - "text": "If a function is intended to be a constructor, it should always be invoked with 'new'. Otherwise, it should always be invoked as a normal function, that is, without 'new'." + { + "id": "KSV009", + "name": "Misconfiguration", + "shortDescription": { + "text": "KSV009" + }, + "fullDescription": { + "text": "Sharing the host’s network namespace permits processes in the pod to communicate with processes bound to the host’s loopback adapter." + }, + "defaultConfiguration": { + "level": "error" + }, + "helpUri": "https://avd.aquasec.com/appshield/ksv009", + "help": { + "text": "Misconfiguration KSV009\nType: Kubernetes Security Check\nSeverity: HIGH\nCheck: Access to host network\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)\nSharing the host’s network namespace permits processes in the pod to communicate with processes bound to the host’s loopback adapter.", + "markdown": "**Misconfiguration KSV009**\n| Type | Severity | Check | Message | Link |\n| --- | --- | --- | --- | --- |\n|Kubernetes Security Check|HIGH|Access to host network|StatefulSet 'statefulset' should not set 'spec.template.spec.hostNetwork' to true|[KSV009](https://avd.aquasec.com/appshield/ksv009)|\n\nSharing the host’s network namespace permits processes in the pod to communicate with processes bound to the host’s loopback adapter." + }, + "properties": { + "precision": "very-high", + "security-severity": "8.0", + "tags": [ + "misconfiguration", + "security", + "HIGH" + ] + } }, - "defaultConfiguration": { - "level": "note" + { + "id": "KSV010", + "name": "Misconfiguration", + "shortDescription": { + "text": "KSV010" + }, + "fullDescription": { + "text": "Sharing the host’s PID namespace allows visibility on host processes, potentially leaking information such as environment variables and configuration." + }, + "defaultConfiguration": { + "level": "error" + }, + "helpUri": "https://avd.aquasec.com/appshield/ksv010", + "help": { + "text": "Misconfiguration KSV010\nType: Kubernetes Security Check\nSeverity: HIGH\nCheck: Access to host PID\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)\nSharing the host’s PID namespace allows visibility on host processes, potentially leaking information such as environment variables and configuration.", + "markdown": "**Misconfiguration KSV010**\n| Type | Severity | Check | Message | Link |\n| --- | --- | --- | --- | --- |\n|Kubernetes Security Check|HIGH|Access to host PID|StatefulSet 'statefulset' should not set 'spec.template.spec.hostPID' to true|[KSV010](https://avd.aquasec.com/appshield/ksv010)|\n\nSharing the host’s PID namespace allows visibility on host processes, potentially leaking information such as environment variables and configuration." + }, + "properties": { + "precision": "very-high", + "security-severity": "8.0", + "tags": [ + "misconfiguration", + "security", + "HIGH" + ] + } }, - "properties": { - "tags": [ - "reliability", - "correctness", - "language-features" - ], - "kind": "problem", - "precision": "very-high", - "problem.severity": "warning" - } - } ] - } - }, - "artifacts" : [ { - "location" : { - "uri" : "main.js", - "uriBaseId" : "%SRCROOT%", - "index" : 0 - } - }, - { - "location": { - "uri": "src/promiseUtils.js", - "uriBaseId": "%SRCROOT%", - "index": 1 - } - }, - { - "location": { - "uri": "src/LiveQueryClient.js", - "uriBaseId": "%SRCROOT%", - "index": 2 - } - }, - { - "location": { - "uri": "src/ParseObject.js", - "uriBaseId": "%SRCROOT%", - "index": 3 - } - } ], - "results" : [ { - "ruleId" : "js/unused-local-variable", - "ruleIndex" : 0, - "message" : { - "text" : "Unused variable foo." - }, - "locations" : [ { - "physicalLocation" : { - "artifactLocation" : { - "uri" : "main.js", - "uriBaseId" : "%SRCROOT%", - "index" : 0 + { + "id": "KSV006", + "name": "Misconfiguration", + "shortDescription": { + "text": "KSV006" + }, + "fullDescription": { + "text": "Mounting docker.sock from the host can give the container full root access to the host." + }, + "defaultConfiguration": { + "level": "error" + }, + "helpUri": "https://avd.aquasec.com/appshield/ksv006", + "help": { + "text": "Misconfiguration KSV006\nType: Kubernetes Security Check\nSeverity: HIGH\nCheck: hostPath volume mounted with docker.sock\nMessage: Pod 'pod' should not specify '/var/run/docker.socker' in 'spec.template.volumes.hostPath.path'\nLink: [KSV006](https://avd.aquasec.com/appshield/ksv006)\nMounting docker.sock from the host can give the container full root access to the host.", + "markdown": "**Misconfiguration KSV006**\n| Type | Severity | Check | Message | Link |\n| --- | --- | --- | --- | --- |\n|Kubernetes Security Check|HIGH|hostPath volume mounted with docker.sock|Pod 'pod' should not specify '/var/run/docker.socker' in 'spec.template.volumes.hostPath.path'|[KSV006](https://avd.aquasec.com/appshield/ksv006)|\n\nMounting docker.sock from the host can give the container full root access to the host." + }, + "properties": { + "precision": "very-high", + "security-severity": "8.0", + "tags": [ + "misconfiguration", + "security", + "HIGH" + ] + } }, - "region" : { - "startLine" : 2, - "startColumn" : 7, - "endColumn" : 10 - } - } - } ], - "partialFingerprints" : { - "primaryLocationLineHash" : "39fa2ee980eb94b0:1", - "primaryLocationStartColumnFingerprint" : "4" + { + "id": "KSV017", + "name": "Misconfiguration", + "shortDescription": { + "text": "KSV017" + }, + "fullDescription": { + "text": "Privileged containers share namespaces with the host system and do not offer any security. They should be used exclusively for system containers that require high privileges." + }, + "defaultConfiguration": { + "level": "error" + }, + "helpUri": "https://avd.aquasec.com/appshield/ksv017", + "help": { + "text": "Misconfiguration KSV017\nType: Kubernetes Security Check\nSeverity: HIGH\nCheck: Privileged container\nMessage: Container 'container' of DaemonSet 'daemonset' should set 'securityContext.privileged' to false\nLink: [KSV017](https://avd.aquasec.com/appshield/ksv017)\nPrivileged containers share namespaces with the host system and do not offer any security. They should be used exclusively for system containers that require high privileges.", + "markdown": "**Misconfiguration KSV017**\n| Type | Severity | Check | Message | Link |\n| --- | --- | --- | --- | --- |\n|Kubernetes Security Check|HIGH|Privileged container|Container 'container' of DaemonSet 'daemonset' should set 'securityContext.privileged' to false|[KSV017](https://avd.aquasec.com/appshield/ksv017)|\n\nPrivileged containers share namespaces with the host system and do not offer any security. They should be used exclusively for system containers that require high privileges." + }, + "properties": { + "precision": "very-high", + "security-severity": "8.0", + "tags": [ + "misconfiguration", + "security", + "HIGH" + ] + } + } + ], + "version": "0.27.1" } }, - { - "ruleId": "js/inconsistent-use-of-new", - "ruleIndex": 1, - "message": { - "text": "Function resolvingPromise is sometimes invoked as a constructor (for example [here](1)), and sometimes as a normal function (for example [here](2))." - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "src/promiseUtils.js", - "uriBaseId": "%SRCROOT%", - "index": 1 - }, - "region": { - "startLine": 2 - } - } - } - ], - "partialFingerprints": { - "primaryLocationLineHash": "5061c3315a741b7d:1", - "primaryLocationStartColumnFingerprint": "7" - }, - "relatedLocations": [ - { - "id": 1, - "physicalLocation": { - "artifactLocation": { - "uri": "src/ParseObject.js", - "uriBaseId": "%SRCROOT%", - "index": 3 - }, - "region": { - "startLine": 2281, - "startColumn": 33, - "endColumn": 55 + "results": [ + { + "ruleId": "KSV008", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "Artifact: auditors/hostns/fixtures/host-ipc-true-allowed.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/hostns/fixtures/host-ipc-true-allowed.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } } - }, - "message": { - "text": "here" } + ] + }, + { + "ruleId": "KSV008", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "Artifact: auditors/hostns/fixtures/host-ipc-true.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" }, - { - "id": 2, - "physicalLocation": { - "artifactLocation": { - "uri": "src/LiveQueryClient.js", - "uriBaseId": "%SRCROOT%", - "index": 2 - }, - "region": { - "startLine": 166 + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/hostns/fixtures/host-ipc-true.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } } - }, - "message": { - "text": "here" - } - } - ] - } ], - "newlineSequences" : [ "\r\n", "\n", "", "" ], - "columnKind" : "utf16CodeUnits", - "properties" : { - "semmle.formatSpecifier" : "sarif-latest" - } + } + ] + }, + { + "ruleId": "KSV009", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Artifact: auditors/hostns/fixtures/host-network-true-allowed.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/hostns/fixtures/host-network-true-allowed.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV009", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Artifact: auditors/hostns/fixtures/host-network-true.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/hostns/fixtures/host-network-true.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV010", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Artifact: auditors/hostns/fixtures/host-pid-true-allowed.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/hostns/fixtures/host-pid-true-allowed.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV010", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Artifact: auditors/hostns/fixtures/host-pid-true.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/hostns/fixtures/host-pid-true.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV008", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "Artifact: auditors/hostns/fixtures/namespaces-all-true-allowed.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/hostns/fixtures/namespaces-all-true-allowed.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV009", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Artifact: auditors/hostns/fixtures/namespaces-all-true-allowed.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/hostns/fixtures/namespaces-all-true-allowed.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV010", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Artifact: auditors/hostns/fixtures/namespaces-all-true-allowed.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/hostns/fixtures/namespaces-all-true-allowed.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV008", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "Artifact: auditors/hostns/fixtures/namespaces-all-true.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/hostns/fixtures/namespaces-all-true.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV009", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Artifact: auditors/hostns/fixtures/namespaces-all-true.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/hostns/fixtures/namespaces-all-true.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV010", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Artifact: auditors/hostns/fixtures/namespaces-all-true.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/hostns/fixtures/namespaces-all-true.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV006", + "ruleIndex": 3, + "level": "error", + "message": { + "text": "Artifact: auditors/mounts/fixtures/docker-sock-mounted.yml\nType: kubernetes\nVulnerability KSV006\nSeverity: HIGH\nMessage: Pod 'pod' should not specify '/var/run/docker.socker' in 'spec.template.volumes.hostPath.path'\nLink: [KSV006](https://avd.aquasec.com/appshield/ksv006)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/mounts/fixtures/docker-sock-mounted.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV017", + "ruleIndex": 4, + "level": "error", + "message": { + "text": "Artifact: auditors/privileged/fixtures/privileged-true-allowed-multi-containers-multi-labels.yml\nType: kubernetes\nVulnerability KSV017\nSeverity: HIGH\nMessage: Container 'container1' of DaemonSet 'daemonset' should set 'securityContext.privileged' to false\nLink: [KSV017](https://avd.aquasec.com/appshield/ksv017)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/privileged/fixtures/privileged-true-allowed-multi-containers-multi-labels.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV017", + "ruleIndex": 4, + "level": "error", + "message": { + "text": "Artifact: auditors/privileged/fixtures/privileged-true-allowed-multi-containers-multi-labels.yml\nType: kubernetes\nVulnerability KSV017\nSeverity: HIGH\nMessage: Container 'container2' of DaemonSet 'daemonset' should set 'securityContext.privileged' to false\nLink: [KSV017](https://avd.aquasec.com/appshield/ksv017)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/privileged/fixtures/privileged-true-allowed-multi-containers-multi-labels.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV017", + "ruleIndex": 4, + "level": "error", + "message": { + "text": "Artifact: auditors/privileged/fixtures/privileged-true-allowed-multi-containers-single-label.yml\nType: kubernetes\nVulnerability KSV017\nSeverity: HIGH\nMessage: Container 'container1' of DaemonSet 'daemonset' should set 'securityContext.privileged' to false\nLink: [KSV017](https://avd.aquasec.com/appshield/ksv017)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/privileged/fixtures/privileged-true-allowed-multi-containers-single-label.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV017", + "ruleIndex": 4, + "level": "error", + "message": { + "text": "Artifact: auditors/privileged/fixtures/privileged-true-allowed-multi-containers-single-label.yml\nType: kubernetes\nVulnerability KSV017\nSeverity: HIGH\nMessage: Container 'container2' of DaemonSet 'daemonset' should set 'securityContext.privileged' to false\nLink: [KSV017](https://avd.aquasec.com/appshield/ksv017)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/privileged/fixtures/privileged-true-allowed-multi-containers-single-label.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV017", + "ruleIndex": 4, + "level": "error", + "message": { + "text": "Artifact: auditors/privileged/fixtures/privileged-true-allowed.yml\nType: kubernetes\nVulnerability KSV017\nSeverity: HIGH\nMessage: Container 'container' of DaemonSet 'daemonset' should set 'securityContext.privileged' to false\nLink: [KSV017](https://avd.aquasec.com/appshield/ksv017)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/privileged/fixtures/privileged-true-allowed.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV017", + "ruleIndex": 4, + "level": "error", + "message": { + "text": "Artifact: auditors/privileged/fixtures/privileged-true.yml\nType: kubernetes\nVulnerability KSV017\nSeverity: HIGH\nMessage: Container 'container' of DaemonSet 'daemonset' should set 'securityContext.privileged' to false\nLink: [KSV017](https://avd.aquasec.com/appshield/ksv017)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "auditors/privileged/fixtures/privileged-true.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV008", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/cronjob.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: CronJob 'cronjob' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/cronjob.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV009", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/cronjob.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: CronJob 'cronjob' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/cronjob.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV010", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/cronjob.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: CronJob 'cronjob' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/cronjob.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV008", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/daemonset-v1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV009", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/daemonset-v1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV010", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/daemonset-v1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV008", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1beta1.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/daemonset-v1beta1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV009", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1beta1.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/daemonset-v1beta1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV010", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1beta1.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/daemonset-v1beta1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV008", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1beta2.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/daemonset-v1beta2.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV009", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1beta2.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/daemonset-v1beta2.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV010", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1beta2.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/daemonset-v1beta2.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV008", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/deployment-apps-v1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV009", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/deployment-apps-v1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV010", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/deployment-apps-v1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV008", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1beta1.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/deployment-apps-v1beta1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV009", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1beta1.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/deployment-apps-v1beta1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV010", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1beta1.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/deployment-apps-v1beta1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV008", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1beta2.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/deployment-apps-v1beta2.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV009", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1beta2.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/deployment-apps-v1beta2.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV010", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1beta2.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/deployment-apps-v1beta2.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV008", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/deployment-extensions-v1beta1.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/deployment-extensions-v1beta1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV009", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/deployment-extensions-v1beta1.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/deployment-extensions-v1beta1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV010", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/deployment-extensions-v1beta1.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/deployment-extensions-v1beta1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV008", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/job.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Job 'job' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/job.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV009", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/job.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Job 'job' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/job.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV010", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/job.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Job 'job' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/job.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV008", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/pod.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/pod.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV009", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/pod.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/pod.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV010", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/pod.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/pod.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV008", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/replicationcontroller.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: ReplicationController 'replicationcontroller' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/replicationcontroller.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV009", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/replicationcontroller.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: ReplicationController 'replicationcontroller' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/replicationcontroller.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV010", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/replicationcontroller.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: ReplicationController 'replicationcontroller' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/replicationcontroller.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV008", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/statefulset-v1.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/statefulset-v1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV009", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/statefulset-v1.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/statefulset-v1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV010", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/statefulset-v1.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/statefulset-v1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV008", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/statefulset-v1beta1.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/statefulset-v1beta1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV009", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/statefulset-v1beta1.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/statefulset-v1beta1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "KSV010", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "Artifact: internal/test/fixtures/all_resources/statefulset-v1beta1.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/test/fixtures/all_resources/statefulset-v1beta1.yml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + } + ] } ] } diff --git a/internal/sarif/sarif-schema.json b/internal/sarif/sarif-schema.json index 41b12d4a..64962a9d 100644 --- a/internal/sarif/sarif-schema.json +++ b/internal/sarif/sarif-schema.json @@ -22,6 +22,7 @@ "description": "The set of runs contained in this log file.", "type": "array", "minItems": 0, + "uniqueItems": false, "items": { "$ref": "#/definitions/run" } @@ -241,6 +242,7 @@ "description": "An array of replacement objects, each of which represents the replacement of a single region in a single artifact specified by 'artifactLocation'.", "type": "array", "minItems": 1, + "uniqueItems": false, "items": { "$ref": "#/definitions/replacement" } @@ -382,6 +384,7 @@ "description": "An array of one or more unique threadFlow objects, each of which describes the progress of a program through a thread of execution.", "type": "array", "minItems": 1, + "uniqueItems": false, "items": { "$ref": "#/definitions/threadFlow" } @@ -556,6 +559,7 @@ "description": "An array of exception objects each of which is considered a cause of this exception.", "type": "array", "minItems": 0, + "uniqueItems": false, "default": [], "items": { "$ref": "#/definitions/exception" @@ -633,6 +637,7 @@ "description": "Describes the invocation of the analysis tool that will be merged with a separate run.", "type": "array", "minItems": 0, + "uniqueItems": false, "default": [], "items": { "$ref": "#/definitions/invocation" @@ -665,6 +670,7 @@ "description": "An array of result objects that will be merged with a separate run.", "type": "array", "minItems": 0, + "uniqueItems": false, "default": [], "items": { "$ref": "#/definitions/result" @@ -724,6 +730,7 @@ "description": "Addresses that will be merged with a separate run.", "type": "array", "minItems": 0, + "uniqueItems": false, "default": [], "items": { "$ref": "#/definitions/address" @@ -1079,6 +1086,7 @@ "description": "The sequences of edges traversed by this graph traversal.", "type": "array", "minItems": 0, + "uniqueItems": false, "default": [], "items": { "$ref": "#/definitions/edgeTraversal" @@ -1111,6 +1119,7 @@ "description": "An array of strings, containing in order the command line arguments passed to the tool from the operating system.", "type": "array", "minItems": 0, + "uniqueItems": false, "items": { "type": "string" } @@ -1169,6 +1178,7 @@ "description": "A list of runtime conditions detected by the tool during the analysis.", "type": "array", "minItems": 0, + "uniqueItems": false, "default": [], "items": { "$ref": "#/definitions/notification" @@ -1179,6 +1189,7 @@ "description": "A list of conditions detected by the tool that are relevant to the tool's configuration.", "type": "array", "minItems": 0, + "uniqueItems": false, "default": [], "items": { "$ref": "#/definitions/notification" @@ -1442,6 +1453,7 @@ "description": "An array of strings to substitute into the message string.", "type": "array", "minItems": 0, + "uniqueItems": false, "default": [], "items": { "type": "string" @@ -2059,6 +2071,7 @@ "description": "The set of locations where the result was detected. Specify only one location unless the problem indicated by the result can only be corrected by making a change at every specified location.", "type": "array", "minItems": 0, + "uniqueItems": false, "default": [], "items": { "$ref": "#/definitions/location" @@ -2114,6 +2127,7 @@ "description": "An array of 'codeFlow' objects relevant to the result.", "type": "array", "minItems": 0, + "uniqueItems": false, "default": [], "items": { "$ref": "#/definitions/codeFlow" @@ -2324,6 +2338,7 @@ "description": "Describes the invocation of the analysis tool.", "type": "array", "minItems": 0, + "uniqueItems": false, "default": [], "items": { "$ref": "#/definitions/invocation" @@ -2339,8 +2354,9 @@ "description": "The language of the messages emitted into the log file during this run (expressed as an ISO 639-1 two-letter lowercase culture code) and an optional region (expressed as an ISO 3166-1 two-letter uppercase subculture code associated with a country or region). The casing is recommended but not required (in order for this data to conform to RFC5646).", "type": "string", "default": "en-US", - "pattern": "^[a-zA-Z]{2}(-[a-zA-Z]{2})?$" + "pattern": "^[a-zA-Z]{2}|^[a-zA-Z]{2}-[a-zA-Z]{2}]?$" }, + "versionControlProvenance": { "description": "Specifies the revision in version control of the artifacts that were scanned.", "type": "array", @@ -2396,6 +2412,7 @@ "description": "The set of results contained in an SARIF log. The results array can be omitted when a run is solely exporting rules metadata. It must be present (but may be empty) if a log file represents an actual scan.", "type": "array", "minItems": 0, + "uniqueItems": false, "items": { "$ref": "#/definitions/result" } @@ -2491,6 +2508,7 @@ "description": "Addresses associated with this run instance, if any.", "type": "array", "minItems": 0, + "uniqueItems": false, "default": [], "items": { "$ref": "#/definitions/address" @@ -2623,6 +2641,7 @@ "description": "An array of stack frames that represents a sequence of calls, rendered in reverse chronological order, that comprise the call stack.", "type": "array", "minItems": 0, + "uniqueItems": false, "items": { "$ref": "#/definitions/stackFrame" } @@ -2661,6 +2680,7 @@ "description": "The parameters of the call that is executing.", "type": "array", "minItems": 0, + "uniqueItems": false, "default": [], "items": { "type": "string", @@ -2759,6 +2779,7 @@ "description": "A temporally ordered array of 'threadFlowLocation' objects, each of which describes a location visited by the tool while producing the result.", "type": "array", "minItems": 1, + "uniqueItems": false, "items": { "$ref": "#/definitions/threadFlowLocation" } @@ -3039,7 +3060,7 @@ "description": "The language of the messages emitted into the log file during this run (expressed as an ISO 639-1 two-letter lowercase language code) and an optional region (expressed as an ISO 3166-1 two-letter uppercase subculture code associated with a country or region). The casing is recommended but not required (in order for this data to conform to RFC5646).", "type": "string", "default": "en-US", - "pattern": "^[a-zA-Z]{2}(-[a-zA-Z]{2})?$" + "pattern": "^[a-zA-Z]{2}|^[a-zA-Z]{2}-[a-zA-Z]{2}]?$" }, "contents": { diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go index 5e8b5928..0d2898ff 100644 --- a/internal/sarif/sarif.go +++ b/internal/sarif/sarif.go @@ -25,6 +25,7 @@ import ( "github.com/Shopify/kubeaudit/auditors/seccomp" "github.com/owenrumney/go-sarif/v2/sarif" "github.com/qri-io/jsonschema" + "github.com/sirupsen/logrus" ) var Auditors = map[string]string{ @@ -105,6 +106,7 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { run.AddResult(result) } + // todo: remove this after trying the library var reportBytes bytes.Buffer err = report.Write(&reportBytes) @@ -112,16 +114,27 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { return nil, nil } - err, errs := Validate(&reportBytes) - + err, errs := validate(&reportBytes) if err != nil { - return nil, fmt.Errorf("error validating SARIF schema: %s. %s", err, errs) + return nil, fmt.Errorf("error validating SARIF schema: %s", err) + } + + if len(errs) > 0 { + for _, errorMsg := range errs { + // not sure if we want to return the errors here + // so just logging them for now + logrus.Info(errorMsg) + } } + // todo: remove validate + return report, nil } -func Validate(report io.Reader) (error, []jsonschema.KeyError) { +// Validates that the SARIF file is valid as per sarif spec +// https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Documents/CommitteeSpecifications/2.1.0/sarif-schema-2.1.0.json +func validate(report io.Reader) (error, []jsonschema.KeyError) { schemaData, err := ioutil.ReadFile("sarif-schema.json") if err != nil { return err, nil @@ -134,7 +147,6 @@ func Validate(report io.Reader) (error, []jsonschema.KeyError) { } _, ok := report.(*bytes.Buffer) - if ok { errs, err := jsonSchema.ValidateBytes(context.Background(), report.(*bytes.Buffer).Bytes()) if err != nil { diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index 7e8ad88b..13884e0f 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -108,9 +108,6 @@ func TestCreate(t *testing.T) { } } -// Validates that the given file path refers to a valid SARIF file. -// Throws an error if the file is invalid. - func TestValidate(t *testing.T) { var reportBytes bytes.Buffer testSarif, err := ioutil.ReadFile("fixtures/valid.sarif") @@ -118,12 +115,12 @@ func TestValidate(t *testing.T) { reportBytes.Write(testSarif) - err, errs := Validate(&reportBytes) + err, errs := validate(&reportBytes) require.NoError(t, err) + if len(errs) > 0 { fmt.Println(errs) } assert.Len(t, errs, 0) - } From bdcc226525ab68e03c74e0ca763ba4671c186a58 Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Tue, 19 Jul 2022 15:25:51 -0400 Subject: [PATCH 11/31] adds sarif validation tests --- go.mod | 3 + go.sum | 3 + internal/sarif/fixtures/invalid.sarif | 92 + internal/sarif/sarif-schema.json | 3370 ------------------------- internal/sarif/sarif.go | 52 +- internal/sarif/sarif_test.go | 39 +- kubeaudit.sarif | 96 + 7 files changed, 241 insertions(+), 3414 deletions(-) create mode 100644 internal/sarif/fixtures/invalid.sarif delete mode 100644 internal/sarif/sarif-schema.json create mode 100644 kubeaudit.sarif diff --git a/go.mod b/go.mod index b4fab07a..4de78030 100644 --- a/go.mod +++ b/go.mod @@ -50,6 +50,9 @@ require ( github.com/qri-io/jsonschema v0.2.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.4.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect diff --git a/go.sum b/go.sum index ddd02873..c8d78ea9 100644 --- a/go.sum +++ b/go.sum @@ -1072,9 +1072,12 @@ github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+ github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= diff --git a/internal/sarif/fixtures/invalid.sarif b/internal/sarif/fixtures/invalid.sarif new file mode 100644 index 00000000..95bdbe44 --- /dev/null +++ b/internal/sarif/fixtures/invalid.sarif @@ -0,0 +1,92 @@ +{ + "version": "2.1.0", + "$schema": "https://json.schemastore.org/sarif-2.1.0-rtm.5.json", + "runs": [ + { + "tool": { + "driver": { + "informationUri": "https://github.com/Shopify/kubeaudit", + "name": "kubeaudit", + "rules": [ + { + "id": "AppArmorInvalidAnnotation", + "name": "apparmor", + "shortDescription": null, + "help": { + "markdown": "**Type**: kubernetes\n**Docs**: https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/apparmor.md\n**Description:** Finds containers that do not have AppArmor enabled" + }, + "properties": { + "precision": "very-high", + "tags": [ + "security", + "kubernetes", + "infrastructure" + ] + } + }, + { + "id": "AutomountServiceAccountTokenTrueAndDefaultSA", + "name": "asat", + "shortDescription": null, + "help": { + "markdown": "**Type**: kubernetes\n**Docs**: \n**Description:** Finds containers where the deprecated SA field is used or with a mounted default SA" + }, + "properties": { + "precision": "very-high", + "tags": [ + "security", + "kubernetes", + "infrastructure" + ] + } + } + ] + } + }, + "results": [ + { + "ruleId": "AppArmorInvalidAnnotation", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "AppArmor annotation key refers to a container that doesn't exist. Remove the annotation 'container.apparmor.security.beta.kubernetes.io/container: badval'." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/sarif/fixtures/apparmor-disabled.yaml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "AutomountServiceAccountTokenTrueAndDefaultSA", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Default service account with token mounted. automountServiceAccountToken should be set to 'false' on either the ServiceAccount or on the PodSpec or a non-default service account should be used." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/sarif/fixtures/apparmor-disabled.yaml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/internal/sarif/sarif-schema.json b/internal/sarif/sarif-schema.json deleted file mode 100644 index 64962a9d..00000000 --- a/internal/sarif/sarif-schema.json +++ /dev/null @@ -1,3370 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Static Analysis Results Format (SARIF) Version 2.1.0 JSON Schema", - "$id": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", - "description": "Static Analysis Results Format (SARIF) Version 2.1.0 JSON Schema: a standard format for the output of static analysis tools.", - "additionalProperties": false, - "type": "object", - "properties": { - - "$schema": { - "description": "The URI of the JSON schema corresponding to the version.", - "type": "string", - "format": "uri" - }, - - "version": { - "description": "The SARIF format version of this log file.", - "enum": [ "2.1.0" ] - }, - - "runs": { - "description": "The set of runs contained in this log file.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "items": { - "$ref": "#/definitions/run" - } - }, - - "inlineExternalProperties": { - "description": "References to external property files that share data between runs.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/externalProperties" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the log file.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "version", "runs" ], - - "definitions": { - - "address": { - "description": "A physical or virtual address, or a range of addresses, in an 'addressable region' (memory or a binary file).", - "additionalProperties": false, - "type": "object", - "properties": { - - "absoluteAddress": { - "description": "The address expressed as a byte offset from the start of the addressable region.", - "type": "integer", - "minimum": -1, - "default": -1 - - }, - - "relativeAddress": { - "description": "The address expressed as a byte offset from the absolute address of the top-most parent object.", - "type": "integer" - - }, - - "length": { - "description": "The number of bytes in this range of addresses.", - "type": "integer" - }, - - "kind": { - "description": "An open-ended string that identifies the address kind. 'data', 'function', 'header','instruction', 'module', 'page', 'section', 'segment', 'stack', 'stackFrame', 'table' are well-known values.", - "type": "string" - }, - - "name": { - "description": "A name that is associated with the address, e.g., '.text'.", - "type": "string" - }, - - "fullyQualifiedName": { - "description": "A human-readable fully qualified name that is associated with the address.", - "type": "string" - }, - - "offsetFromParent": { - "description": "The byte offset of this address from the absolute or relative address of the parent object.", - "type": "integer" - }, - - "index": { - "description": "The index within run.addresses of the cached object for this address.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "parentIndex": { - "description": "The index within run.addresses of the parent object.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the address.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "artifact": { - "description": "A single artifact. In some cases, this artifact might be nested within another artifact.", - "additionalProperties": false, - "type": "object", - "properties": { - - "description": { - "description": "A short description of the artifact.", - "$ref": "#/definitions/message" - }, - - "location": { - "description": "The location of the artifact.", - "$ref": "#/definitions/artifactLocation" - }, - - "parentIndex": { - "description": "Identifies the index of the immediate parent of the artifact, if this artifact is nested.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "offset": { - "description": "The offset in bytes of the artifact within its containing artifact.", - "type": "integer", - "minimum": 0 - }, - - "length": { - "description": "The length of the artifact in bytes.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "roles": { - "description": "The role or roles played by the artifact in the analysis.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "enum": [ - "analysisTarget", - "attachment", - "responseFile", - "resultFile", - "standardStream", - "tracedFile", - "unmodified", - "modified", - "added", - "deleted", - "renamed", - "uncontrolled", - "driver", - "extension", - "translation", - "taxonomy", - "policy", - "referencedOnCommandLine", - "memoryContents", - "directory", - "userSpecifiedConfiguration", - "toolSpecifiedConfiguration", - "debugOutputFile" - ] - } - }, - - "mimeType": { - "description": "The MIME type (RFC 2045) of the artifact.", - "type": "string", - "pattern": "[^/]+/.+" - }, - - "contents": { - "description": "The contents of the artifact.", - "$ref": "#/definitions/artifactContent" - }, - - "encoding": { - "description": "Specifies the encoding for an artifact object that refers to a text file.", - "type": "string" - }, - - "sourceLanguage": { - "description": "Specifies the source language for any artifact object that refers to a text file that contains source code.", - "type": "string" - }, - - "hashes": { - "description": "A dictionary, each of whose keys is the name of a hash function and each of whose values is the hashed value of the artifact produced by the specified hash function.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - - "lastModifiedTimeUtc": { - "description": "The Coordinated Universal Time (UTC) date and time at which the artifact was most recently modified. See \"Date/time properties\" in the SARIF spec for the required format.", - "type": "string", - "format": "date-time" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the artifact.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "artifactChange": { - "description": "A change to a single artifact.", - "additionalProperties": false, - "type": "object", - "properties": { - - "artifactLocation": { - "description": "The location of the artifact to change.", - "$ref": "#/definitions/artifactLocation" - }, - - "replacements": { - "description": "An array of replacement objects, each of which represents the replacement of a single region in a single artifact specified by 'artifactLocation'.", - "type": "array", - "minItems": 1, - "uniqueItems": false, - "items": { - "$ref": "#/definitions/replacement" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the change.", - "$ref": "#/definitions/propertyBag" - } - - }, - - "required": [ "artifactLocation", "replacements" ] - }, - - "artifactContent": { - "description": "Represents the contents of an artifact.", - "type": "object", - "additionalProperties": false, - "properties": { - - "text": { - "description": "UTF-8-encoded content from a text artifact.", - "type": "string" - }, - - "binary": { - "description": "MIME Base64-encoded content from a binary artifact, or from a text artifact in its original encoding.", - "type": "string" - }, - - "rendered": { - "description": "An alternate rendered representation of the artifact (e.g., a decompiled representation of a binary region).", - "$ref": "#/definitions/multiformatMessageString" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the artifact content.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "artifactLocation": { - "description": "Specifies the location of an artifact.", - "additionalProperties": false, - "type": "object", - "properties": { - - "uri": { - "description": "A string containing a valid relative or absolute URI.", - "type": "string", - "format": "uri-reference" - }, - - "uriBaseId": { - "description": "A string which indirectly specifies the absolute URI with respect to which a relative URI in the \"uri\" property is interpreted.", - "type": "string" - }, - - "index": { - "description": "The index within the run artifacts array of the artifact object associated with the artifact location.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "description": { - "description": "A short description of the artifact location.", - "$ref": "#/definitions/message" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the artifact location.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "attachment": { - "description": "An artifact relevant to a result.", - "type": "object", - "additionalProperties": false, - "properties": { - - "description": { - "description": "A message describing the role played by the attachment.", - "$ref": "#/definitions/message" - }, - - "artifactLocation": { - "description": "The location of the attachment.", - "$ref": "#/definitions/artifactLocation" - }, - - "regions": { - "description": "An array of regions of interest within the attachment.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/region" - } - }, - - "rectangles": { - "description": "An array of rectangles specifying areas of interest within the image.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/rectangle" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the attachment.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "artifactLocation" ] - }, - - "codeFlow": { - "description": "A set of threadFlows which together describe a pattern of code execution relevant to detecting a result.", - "additionalProperties": false, - "type": "object", - "properties": { - - "message": { - "description": "A message relevant to the code flow.", - "$ref": "#/definitions/message" - }, - - "threadFlows": { - "description": "An array of one or more unique threadFlow objects, each of which describes the progress of a program through a thread of execution.", - "type": "array", - "minItems": 1, - "uniqueItems": false, - "items": { - "$ref": "#/definitions/threadFlow" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the code flow.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "threadFlows" ] - }, - - "configurationOverride": { - "description": "Information about how a specific rule or notification was reconfigured at runtime.", - "type": "object", - "additionalProperties": false, - "properties": { - - "configuration": { - "description": "Specifies how the rule or notification was configured during the scan.", - "$ref": "#/definitions/reportingConfiguration" - }, - - "descriptor": { - "description": "A reference used to locate the descriptor whose configuration was overridden.", - "$ref": "#/definitions/reportingDescriptorReference" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the configuration override.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "configuration", "descriptor" ] - }, - - "conversion": { - "description": "Describes how a converter transformed the output of a static analysis tool from the analysis tool's native output format into the SARIF format.", - "additionalProperties": false, - "type": "object", - "properties": { - - "tool": { - "description": "A tool object that describes the converter.", - "$ref": "#/definitions/tool" - }, - - "invocation": { - "description": "An invocation object that describes the invocation of the converter.", - "$ref": "#/definitions/invocation" - }, - - "analysisToolLogFiles": { - "description": "The locations of the analysis tool's per-run log files.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/artifactLocation" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the conversion.", - "$ref": "#/definitions/propertyBag" - } - - }, - - "required": [ "tool" ] - }, - - "edge": { - "description": "Represents a directed edge in a graph.", - "type": "object", - "additionalProperties": false, - "properties": { - - "id": { - "description": "A string that uniquely identifies the edge within its graph.", - "type": "string" - }, - - "label": { - "description": "A short description of the edge.", - "$ref": "#/definitions/message" - }, - - "sourceNodeId": { - "description": "Identifies the source node (the node at which the edge starts).", - "type": "string" - }, - - "targetNodeId": { - "description": "Identifies the target node (the node at which the edge ends).", - "type": "string" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the edge.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "id", "sourceNodeId", "targetNodeId" ] - }, - - "edgeTraversal": { - "description": "Represents the traversal of a single edge during a graph traversal.", - "type": "object", - "additionalProperties": false, - "properties": { - - "edgeId": { - "description": "Identifies the edge being traversed.", - "type": "string" - }, - - "message": { - "description": "A message to display to the user as the edge is traversed.", - "$ref": "#/definitions/message" - }, - - "finalState": { - "description": "The values of relevant expressions after the edge has been traversed.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/multiformatMessageString" - } - }, - - "stepOverEdgeCount": { - "description": "The number of edge traversals necessary to return from a nested graph.", - "type": "integer", - "minimum": 0 - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the edge traversal.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "edgeId" ] - }, - - "exception": { - "description": "Describes a runtime exception encountered during the execution of an analysis tool.", - "type": "object", - "additionalProperties": false, - "properties": { - - "kind": { - "type": "string", - "description": "A string that identifies the kind of exception, for example, the fully qualified type name of an object that was thrown, or the symbolic name of a signal." - }, - - "message": { - "description": "A message that describes the exception.", - "type": "string" - }, - - "stack": { - "description": "The sequence of function calls leading to the exception.", - "$ref": "#/definitions/stack" - }, - - "innerExceptions": { - "description": "An array of exception objects each of which is considered a cause of this exception.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/exception" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the exception.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "externalProperties": { - "description": "The top-level element of an external property file.", - "type": "object", - "additionalProperties": false, - "properties": { - - "schema": { - "description": "The URI of the JSON schema corresponding to the version of the external property file format.", - "type": "string", - "format": "uri" - }, - - "version": { - "description": "The SARIF format version of this external properties object.", - "enum": [ "2.1.0" ] - }, - - "guid": { - "description": "A stable, unique identifer for this external properties object, in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "runGuid": { - "description": "A stable, unique identifer for the run associated with this external properties object, in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "conversion": { - "description": "A conversion object that will be merged with a separate run.", - "$ref": "#/definitions/conversion" - }, - - "graphs": { - "description": "An array of graph objects that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "default": [], - "uniqueItems": true, - "items": { - "$ref": "#/definitions/graph" - } - }, - - "externalizedProperties": { - "description": "Key/value pairs that provide additional information that will be merged with a separate run.", - "$ref": "#/definitions/propertyBag" - }, - - "artifacts": { - "description": "An array of artifact objects that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/artifact" - } - }, - - "invocations": { - "description": "Describes the invocation of the analysis tool that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/invocation" - } - }, - - "logicalLocations": { - "description": "An array of logical locations such as namespaces, types or functions that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/logicalLocation" - } - }, - - "threadFlowLocations": { - "description": "An array of threadFlowLocation objects that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/threadFlowLocation" - } - }, - - "results": { - "description": "An array of result objects that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/result" - } - }, - - "taxonomies": { - "description": "Tool taxonomies that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponent" - } - }, - - "driver": { - "description": "The analysis tool object that will be merged with a separate run.", - "$ref": "#/definitions/toolComponent" - }, - - "extensions": { - "description": "Tool extensions that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponent" - } - }, - - "policies": { - "description": "Tool policies that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponent" - } - }, - - "translations": { - "description": "Tool translations that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponent" - } - }, - - "addresses": { - "description": "Addresses that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/address" - } - }, - - "webRequests": { - "description": "Requests that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/webRequest" - } - }, - - "webResponses": { - "description": "Responses that will be merged with a separate run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/webResponse" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the external properties.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "externalPropertyFileReference": { - "description": "Contains information that enables a SARIF consumer to locate the external property file that contains the value of an externalized property associated with the run.", - "type": "object", - "additionalProperties": false, - "properties": { - - "location": { - "description": "The location of the external property file.", - "$ref": "#/definitions/artifactLocation" - }, - - "guid": { - "description": "A stable, unique identifer for the external property file in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "itemCount": { - "description": "A non-negative integer specifying the number of items contained in the external property file.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the external property file.", - "$ref": "#/definitions/propertyBag" - } - }, - "anyOf": [ - { "required": [ "location" ] }, - { "required": [ "guid" ] } - ] - }, - - "externalPropertyFileReferences": { - "description": "References to external property files that should be inlined with the content of a root log file.", - "additionalProperties": false, - "type": "object", - "properties": { - - "conversion": { - "description": "An external property file containing a run.conversion object to be merged with the root log file.", - "$ref": "#/definitions/externalPropertyFileReference" - }, - - "graphs": { - "description": "An array of external property files containing a run.graphs object to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "externalizedProperties": { - "description": "An external property file containing a run.properties object to be merged with the root log file.", - "$ref": "#/definitions/externalPropertyFileReference" - }, - - "artifacts": { - "description": "An array of external property files containing run.artifacts arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "invocations": { - "description": "An array of external property files containing run.invocations arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "logicalLocations": { - "description": "An array of external property files containing run.logicalLocations arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "threadFlowLocations": { - "description": "An array of external property files containing run.threadFlowLocations arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "results": { - "description": "An array of external property files containing run.results arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "taxonomies": { - "description": "An array of external property files containing run.taxonomies arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "addresses": { - "description": "An array of external property files containing run.addresses arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "driver": { - "description": "An external property file containing a run.driver object to be merged with the root log file.", - "$ref": "#/definitions/externalPropertyFileReference" - }, - - "extensions": { - "description": "An array of external property files containing run.extensions arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "policies": { - "description": "An array of external property files containing run.policies arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "translations": { - "description": "An array of external property files containing run.translations arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "webRequests": { - "description": "An array of external property files containing run.requests arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "webResponses": { - "description": "An array of external property files containing run.responses arrays to be merged with the root log file.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/externalPropertyFileReference" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the external property files.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "fix": { - "description": "A proposed fix for the problem represented by a result object. A fix specifies a set of artifacts to modify. For each artifact, it specifies a set of bytes to remove, and provides a set of new bytes to replace them.", - "additionalProperties": false, - "type": "object", - "properties": { - - "description": { - "description": "A message that describes the proposed fix, enabling viewers to present the proposed change to an end user.", - "$ref": "#/definitions/message" - }, - - "artifactChanges": { - "description": "One or more artifact changes that comprise a fix for a result.", - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/artifactChange" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the fix.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "artifactChanges" ] - }, - - "graph": { - "description": "A network of nodes and directed edges that describes some aspect of the structure of the code (for example, a call graph).", - "type": "object", - "additionalProperties": false, - "properties": { - - "description": { - "description": "A description of the graph.", - "$ref": "#/definitions/message" - }, - - "nodes": { - "description": "An array of node objects representing the nodes of the graph.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/node" - } - }, - - "edges": { - "description": "An array of edge objects representing the edges of the graph.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/edge" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the graph.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "graphTraversal": { - "description": "Represents a path through a graph.", - "type": "object", - "additionalProperties": false, - "properties": { - - "runGraphIndex": { - "description": "The index within the run.graphs to be associated with the result.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "resultGraphIndex": { - "description": "The index within the result.graphs to be associated with the result.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "description": { - "description": "A description of this graph traversal.", - "$ref": "#/definitions/message" - }, - - "initialState": { - "description": "Values of relevant expressions at the start of the graph traversal that may change during graph traversal.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/multiformatMessageString" - } - }, - - "immutableState": { - "description": "Values of relevant expressions at the start of the graph traversal that remain constant for the graph traversal.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/multiformatMessageString" - } - }, - - "edgeTraversals": { - "description": "The sequences of edges traversed by this graph traversal.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/edgeTraversal" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the graph traversal.", - "$ref": "#/definitions/propertyBag" - } - }, - "oneOf": [ - { "required": [ "runGraphIndex" ] }, - { "required": [ "resultGraphIndex" ] } - ] - }, - - "invocation": { - "description": "The runtime environment of the analysis tool run.", - "additionalProperties": false, - "type": "object", - "properties": { - - "commandLine": { - "description": "The command line used to invoke the tool.", - "type": "string" - }, - - "arguments": { - "description": "An array of strings, containing in order the command line arguments passed to the tool from the operating system.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "items": { - "type": "string" - } - }, - - "responseFiles": { - "description": "The locations of any response files specified on the tool's command line.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/artifactLocation" - } - }, - - "startTimeUtc": { - "description": "The Coordinated Universal Time (UTC) date and time at which the run started. See \"Date/time properties\" in the SARIF spec for the required format.", - "type": "string", - "format": "date-time" - }, - - "endTimeUtc": { - "description": "The Coordinated Universal Time (UTC) date and time at which the run ended. See \"Date/time properties\" in the SARIF spec for the required format.", - "type": "string", - "format": "date-time" - }, - - "exitCode": { - "description": "The process exit code.", - "type": "integer" - }, - - "ruleConfigurationOverrides": { - "description": "An array of configurationOverride objects that describe rules related runtime overrides.", - "type": "array", - "minItems": 0, - "default": [], - "uniqueItems": true, - "items": { - "$ref": "#/definitions/configurationOverride" - } - }, - - "notificationConfigurationOverrides": { - "description": "An array of configurationOverride objects that describe notifications related runtime overrides.", - "type": "array", - "minItems": 0, - "default": [], - "uniqueItems": true, - "items": { - "$ref": "#/definitions/configurationOverride" - } - }, - - "toolExecutionNotifications": { - "description": "A list of runtime conditions detected by the tool during the analysis.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/notification" - } - }, - - "toolConfigurationNotifications": { - "description": "A list of conditions detected by the tool that are relevant to the tool's configuration.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/notification" - } - }, - - "exitCodeDescription": { - "description": "The reason for the process exit.", - "type": "string" - }, - - "exitSignalName": { - "description": "The name of the signal that caused the process to exit.", - "type": "string" - }, - - "exitSignalNumber": { - "description": "The numeric value of the signal that caused the process to exit.", - "type": "integer" - }, - - "processStartFailureMessage": { - "description": "The reason given by the operating system that the process failed to start.", - "type": "string" - }, - - "executionSuccessful": { - "description": "Specifies whether the tool's execution completed successfully.", - "type": "boolean" - }, - - "machine": { - "description": "The machine that hosted the analysis tool run.", - "type": "string" - }, - - "account": { - "description": "The account that ran the analysis tool.", - "type": "string" - }, - - "processId": { - "description": "The process id for the analysis tool run.", - "type": "integer" - }, - - "executableLocation": { - "description": "An absolute URI specifying the location of the analysis tool's executable.", - "$ref": "#/definitions/artifactLocation" - }, - - "workingDirectory": { - "description": "The working directory for the analysis tool run.", - "$ref": "#/definitions/artifactLocation" - }, - - "environmentVariables": { - "description": "The environment variables associated with the analysis tool process, expressed as key/value pairs.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - - "stdin": { - "description": "A file containing the standard input stream to the process that was invoked.", - "$ref": "#/definitions/artifactLocation" - }, - - "stdout": { - "description": "A file containing the standard output stream from the process that was invoked.", - "$ref": "#/definitions/artifactLocation" - }, - - "stderr": { - "description": "A file containing the standard error stream from the process that was invoked.", - "$ref": "#/definitions/artifactLocation" - }, - - "stdoutStderr": { - "description": "A file containing the interleaved standard output and standard error stream from the process that was invoked.", - "$ref": "#/definitions/artifactLocation" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the invocation.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "executionSuccessful" ] - }, - - "location": { - "description": "A location within a programming artifact.", - "additionalProperties": false, - "type": "object", - "properties": { - - "id": { - "description": "Value that distinguishes this location from all other locations within a single result object.", - "type": "integer", - "minimum": -1, - "default": -1 - }, - - "physicalLocation": { - "description": "Identifies the artifact and region.", - "$ref": "#/definitions/physicalLocation" - }, - - "logicalLocations": { - "description": "The logical locations associated with the result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/logicalLocation" - } - }, - - "message": { - "description": "A message relevant to the location.", - "$ref": "#/definitions/message" - }, - - "annotations": { - "description": "A set of regions relevant to the location.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/region" - } - }, - - "relationships": { - "description": "An array of objects that describe relationships between this location and others.", - "type": "array", - "default": [], - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/locationRelationship" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the location.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "locationRelationship": { - "description": "Information about the relation of one location to another.", - "type": "object", - "additionalProperties": false, - "properties": { - - "target": { - "description": "A reference to the related location.", - "type": "integer", - "minimum": 0 - }, - - "kinds": { - "description": "A set of distinct strings that categorize the relationship. Well-known kinds include 'includes', 'isIncludedBy' and 'relevant'.", - "type": "array", - "default": [ "relevant" ], - "uniqueItems": true, - "items": { - "type": "string" - } - }, - - "description": { - "description": "A description of the location relationship.", - "$ref": "#/definitions/message" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the location relationship.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "target" ] - }, - - "logicalLocation": { - "description": "A logical location of a construct that produced a result.", - "additionalProperties": false, - "type": "object", - "properties": { - - "name": { - "description": "Identifies the construct in which the result occurred. For example, this property might contain the name of a class or a method.", - "type": "string" - }, - - "index": { - "description": "The index within the logical locations array.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "fullyQualifiedName": { - "description": "The human-readable fully qualified name of the logical location.", - "type": "string" - }, - - "decoratedName": { - "description": "The machine-readable name for the logical location, such as a mangled function name provided by a C++ compiler that encodes calling convention, return type and other details along with the function name.", - "type": "string" - }, - - "parentIndex": { - "description": "Identifies the index of the immediate parent of the construct in which the result was detected. For example, this property might point to a logical location that represents the namespace that holds a type.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "kind": { - "description": "The type of construct this logical location component refers to. Should be one of 'function', 'member', 'module', 'namespace', 'parameter', 'resource', 'returnType', 'type', 'variable', 'object', 'array', 'property', 'value', 'element', 'text', 'attribute', 'comment', 'declaration', 'dtd' or 'processingInstruction', if any of those accurately describe the construct.", - "type": "string" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the logical location.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "message": { - "description": "Encapsulates a message intended to be read by the end user.", - "type": "object", - "additionalProperties": false, - - "properties": { - - "text": { - "description": "A plain text message string.", - "type": "string" - }, - - "markdown": { - "description": "A Markdown message string.", - "type": "string" - }, - - "id": { - "description": "The identifier for this message.", - "type": "string" - }, - - "arguments": { - "description": "An array of strings to substitute into the message string.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "type": "string" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the message.", - "$ref": "#/definitions/propertyBag" - } - }, - "anyOf": [ - { "required": [ "text" ] }, - { "required": [ "id" ] } - ] - }, - - "multiformatMessageString": { - "description": "A message string or message format string rendered in multiple formats.", - "type": "object", - "additionalProperties": false, - - "properties": { - - "text": { - "description": "A plain text message string or format string.", - "type": "string" - }, - - "markdown": { - "description": "A Markdown message string or format string.", - "type": "string" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the message.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "text" ] - }, - - "node": { - "description": "Represents a node in a graph.", - "type": "object", - "additionalProperties": false, - - "properties": { - - "id": { - "description": "A string that uniquely identifies the node within its graph.", - "type": "string" - }, - - "label": { - "description": "A short description of the node.", - "$ref": "#/definitions/message" - }, - - "location": { - "description": "A code location associated with the node.", - "$ref": "#/definitions/location" - }, - - "children": { - "description": "Array of child nodes.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/node" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the node.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "id" ] - }, - - "notification": { - "description": "Describes a condition relevant to the tool itself, as opposed to being relevant to a target being analyzed by the tool.", - "type": "object", - "additionalProperties": false, - "properties": { - - "locations": { - "description": "The locations relevant to this notification.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/location" - } - }, - - "message": { - "description": "A message that describes the condition that was encountered.", - "$ref": "#/definitions/message" - }, - - "level": { - "description": "A value specifying the severity level of the notification.", - "default": "warning", - "enum": [ "none", "note", "warning", "error" ] - }, - - "threadId": { - "description": "The thread identifier of the code that generated the notification.", - "type": "integer" - }, - - "timeUtc": { - "description": "The Coordinated Universal Time (UTC) date and time at which the analysis tool generated the notification.", - "type": "string", - "format": "date-time" - }, - - "exception": { - "description": "The runtime exception, if any, relevant to this notification.", - "$ref": "#/definitions/exception" - }, - - "descriptor": { - "description": "A reference used to locate the descriptor relevant to this notification.", - "$ref": "#/definitions/reportingDescriptorReference" - }, - - "associatedRule": { - "description": "A reference used to locate the rule descriptor associated with this notification.", - "$ref": "#/definitions/reportingDescriptorReference" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the notification.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "message" ] - }, - - "physicalLocation": { - "description": "A physical location relevant to a result. Specifies a reference to a programming artifact together with a range of bytes or characters within that artifact.", - "additionalProperties": false, - "type": "object", - "properties": { - - "address": { - "description": "The address of the location.", - "$ref": "#/definitions/address" - }, - - "artifactLocation": { - "description": "The location of the artifact.", - "$ref": "#/definitions/artifactLocation" - }, - - "region": { - "description": "Specifies a portion of the artifact.", - "$ref": "#/definitions/region" - }, - - "contextRegion": { - "description": "Specifies a portion of the artifact that encloses the region. Allows a viewer to display additional context around the region.", - "$ref": "#/definitions/region" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the physical location.", - "$ref": "#/definitions/propertyBag" - } - }, - - "anyOf": [ - { - "required": [ "address" ] - }, - { - "required": [ "artifactLocation" ] - } - ] - }, - - "propertyBag": { - "description": "Key/value pairs that provide additional information about the object.", - "type": "object", - "additionalProperties": true, - "properties": { - "tags": { - - "description": "A set of distinct strings that provide additional information.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "type": "string" - } - } - } - }, - - "rectangle": { - "description": "An area within an image.", - "additionalProperties": false, - "type": "object", - "properties": { - - "top": { - "description": "The Y coordinate of the top edge of the rectangle, measured in the image's natural units.", - "type": "number" - }, - - "left": { - "description": "The X coordinate of the left edge of the rectangle, measured in the image's natural units.", - "type": "number" - }, - - "bottom": { - "description": "The Y coordinate of the bottom edge of the rectangle, measured in the image's natural units.", - "type": "number" - }, - - "right": { - "description": "The X coordinate of the right edge of the rectangle, measured in the image's natural units.", - "type": "number" - }, - - "message": { - "description": "A message relevant to the rectangle.", - "$ref": "#/definitions/message" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the rectangle.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "region": { - "description": "A region within an artifact where a result was detected.", - "additionalProperties": false, - "type": "object", - "properties": { - - "startLine": { - "description": "The line number of the first character in the region.", - "type": "integer", - "minimum": 1 - }, - - "startColumn": { - "description": "The column number of the first character in the region.", - "type": "integer", - "minimum": 1 - }, - - "endLine": { - "description": "The line number of the last character in the region.", - "type": "integer", - "minimum": 1 - }, - - "endColumn": { - "description": "The column number of the character following the end of the region.", - "type": "integer", - "minimum": 1 - }, - - "charOffset": { - "description": "The zero-based offset from the beginning of the artifact of the first character in the region.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "charLength": { - "description": "The length of the region in characters.", - "type": "integer", - "minimum": 0 - }, - - "byteOffset": { - "description": "The zero-based offset from the beginning of the artifact of the first byte in the region.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "byteLength": { - "description": "The length of the region in bytes.", - "type": "integer", - "minimum": 0 - }, - - "snippet": { - "description": "The portion of the artifact contents within the specified region.", - "$ref": "#/definitions/artifactContent" - }, - - "message": { - "description": "A message relevant to the region.", - "$ref": "#/definitions/message" - }, - - "sourceLanguage": { - "description": "Specifies the source language, if any, of the portion of the artifact specified by the region object.", - "type": "string" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the region.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "replacement": { - "description": "The replacement of a single region of an artifact.", - "additionalProperties": false, - "type": "object", - "properties": { - - "deletedRegion": { - "description": "The region of the artifact to delete.", - "$ref": "#/definitions/region" - }, - - "insertedContent": { - "description": "The content to insert at the location specified by the 'deletedRegion' property.", - "$ref": "#/definitions/artifactContent" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the replacement.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "deletedRegion" ] - }, - - "reportingDescriptor": { - "description": "Metadata that describes a specific report produced by the tool, as part of the analysis it provides or its runtime reporting.", - "additionalProperties": false, - "type": "object", - "properties": { - - "id": { - "description": "A stable, opaque identifier for the report.", - "type": "string" - }, - - "deprecatedIds": { - "description": "An array of stable, opaque identifiers by which this report was known in some previous version of the analysis tool.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "type": "string" - } - }, - - "guid": { - "description": "A unique identifer for the reporting descriptor in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "deprecatedGuids": { - "description": "An array of unique identifies in the form of a GUID by which this report was known in some previous version of the analysis tool.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - } - }, - - "name": { - "description": "A report identifier that is understandable to an end user.", - "type": "string" - }, - - "deprecatedNames": { - "description": "An array of readable identifiers by which this report was known in some previous version of the analysis tool.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "type": "string" - } - }, - - "shortDescription": { - "description": "A concise description of the report. Should be a single sentence that is understandable when visible space is limited to a single line of text.", - "$ref": "#/definitions/multiformatMessageString" - }, - - "fullDescription": { - "description": "A description of the report. Should, as far as possible, provide details sufficient to enable resolution of any problem indicated by the result.", - "$ref": "#/definitions/multiformatMessageString" - }, - - "messageStrings": { - "description": "A set of name/value pairs with arbitrary names. Each value is a multiformatMessageString object, which holds message strings in plain text and (optionally) Markdown format. The strings can include placeholders, which can be used to construct a message in combination with an arbitrary number of additional string arguments.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/multiformatMessageString" - } - }, - - "defaultConfiguration": { - "description": "Default reporting configuration information.", - "$ref": "#/definitions/reportingConfiguration" - }, - - "helpUri": { - "description": "A URI where the primary documentation for the report can be found.", - "type": "string", - "format": "uri" - }, - - "help": { - "description": "Provides the primary documentation for the report, useful when there is no online documentation.", - "$ref": "#/definitions/multiformatMessageString" - }, - - "relationships": { - "description": "An array of objects that describe relationships between this reporting descriptor and others.", - "type": "array", - "default": [], - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/reportingDescriptorRelationship" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the report.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "id" ] - }, - - "reportingConfiguration": { - "description": "Information about a rule or notification that can be configured at runtime.", - "type": "object", - "additionalProperties": false, - "properties": { - - "enabled": { - "description": "Specifies whether the report may be produced during the scan.", - "type": "boolean", - "default": true - }, - - "level": { - "description": "Specifies the failure level for the report.", - "default": "warning", - "enum": [ "none", "note", "warning", "error" ] - }, - - "rank": { - "description": "Specifies the relative priority of the report. Used for analysis output only.", - "type": "number", - "default": -1.0, - "minimum": -1.0, - "maximum": 100.0 - }, - - "parameters": { - "description": "Contains configuration information specific to a report.", - "$ref": "#/definitions/propertyBag" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the reporting configuration.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "reportingDescriptorReference": { - "description": "Information about how to locate a relevant reporting descriptor.", - "type": "object", - "additionalProperties": false, - "properties": { - - "id": { - "description": "The id of the descriptor.", - "type": "string" - }, - - "index": { - "description": "The index into an array of descriptors in toolComponent.ruleDescriptors, toolComponent.notificationDescriptors, or toolComponent.taxonomyDescriptors, depending on context.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "guid": { - "description": "A guid that uniquely identifies the descriptor.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "toolComponent": { - "description": "A reference used to locate the toolComponent associated with the descriptor.", - "$ref": "#/definitions/toolComponentReference" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the reporting descriptor reference.", - "$ref": "#/definitions/propertyBag" - } - }, - "anyOf": [ - { "required": [ "index" ] }, - { "required": [ "guid" ] }, - { "required": [ "id" ] } - ] - }, - - "reportingDescriptorRelationship": { - "description": "Information about the relation of one reporting descriptor to another.", - "type": "object", - "additionalProperties": false, - "properties": { - - "target": { - "description": "A reference to the related reporting descriptor.", - "$ref": "#/definitions/reportingDescriptorReference" - }, - - "kinds": { - "description": "A set of distinct strings that categorize the relationship. Well-known kinds include 'canPrecede', 'canFollow', 'willPrecede', 'willFollow', 'superset', 'subset', 'equal', 'disjoint', 'relevant', and 'incomparable'.", - "type": "array", - "default": [ "relevant" ], - "uniqueItems": true, - "items": { - "type": "string" - } - }, - - "description": { - "description": "A description of the reporting descriptor relationship.", - "$ref": "#/definitions/message" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the reporting descriptor reference.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "target" ] - }, - - "result": { - "description": "A result produced by an analysis tool.", - "additionalProperties": false, - "type": "object", - "properties": { - - "ruleId": { - "description": "The stable, unique identifier of the rule, if any, to which this notification is relevant. This member can be used to retrieve rule metadata from the rules dictionary, if it exists.", - "type": "string" - }, - - "ruleIndex": { - "description": "The index within the tool component rules array of the rule object associated with this result.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "rule": { - "description": "A reference used to locate the rule descriptor relevant to this result.", - "$ref": "#/definitions/reportingDescriptorReference" - }, - - "kind": { - "description": "A value that categorizes results by evaluation state.", - "default": "fail", - "enum": [ "notApplicable", "pass", "fail", "review", "open", "informational" ] - }, - - "level": { - "description": "A value specifying the severity level of the result.", - "default": "warning", - "enum": [ "none", "note", "warning", "error" ] - }, - - "message": { - "description": "A message that describes the result. The first sentence of the message only will be displayed when visible space is limited.", - "$ref": "#/definitions/message" - }, - - "analysisTarget": { - "description": "Identifies the artifact that the analysis tool was instructed to scan. This need not be the same as the artifact where the result actually occurred.", - "$ref": "#/definitions/artifactLocation" - }, - - "locations": { - "description": "The set of locations where the result was detected. Specify only one location unless the problem indicated by the result can only be corrected by making a change at every specified location.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/location" - } - }, - - "guid": { - "description": "A stable, unique identifer for the result in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "correlationGuid": { - "description": "A stable, unique identifier for the equivalence class of logically identical results to which this result belongs, in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "occurrenceCount": { - "description": "A positive integer specifying the number of times this logically unique result was observed in this run.", - "type": "integer", - "minimum": 1 - }, - - "partialFingerprints": { - "description": "A set of strings that contribute to the stable, unique identity of the result.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - - "fingerprints": { - "description": "A set of strings each of which individually defines a stable, unique identity for the result.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - - "stacks": { - "description": "An array of 'stack' objects relevant to the result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/stack" - } - }, - - "codeFlows": { - "description": "An array of 'codeFlow' objects relevant to the result.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/codeFlow" - } - }, - - "graphs": { - "description": "An array of zero or more unique graph objects associated with the result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/graph" - } - }, - - "graphTraversals": { - "description": "An array of one or more unique 'graphTraversal' objects.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/graphTraversal" - } - }, - - "relatedLocations": { - "description": "A set of locations relevant to this result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/location" - } - }, - - "suppressions": { - "description": "A set of suppressions relevant to this result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/suppression" - } - }, - - "baselineState": { - "description": "The state of a result relative to a baseline of a previous run.", - "enum": [ - "new", - "unchanged", - "updated", - "absent" - ] - }, - - "rank": { - "description": "A number representing the priority or importance of the result.", - "type": "number", - "default": -1.0, - "minimum": -1.0, - "maximum": 100.0 - }, - - "attachments": { - "description": "A set of artifacts relevant to the result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/attachment" - } - }, - - "hostedViewerUri": { - "description": "An absolute URI at which the result can be viewed.", - "type": "string", - "format": "uri" - }, - - "workItemUris": { - "description": "The URIs of the work items associated with this result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "type": "string", - "format": "uri" - } - }, - - "provenance": { - "description": "Information about how and when the result was detected.", - "$ref": "#/definitions/resultProvenance" - }, - - "fixes": { - "description": "An array of 'fix' objects, each of which represents a proposed fix to the problem indicated by the result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/fix" - } - }, - - "taxa": { - "description": "An array of references to taxonomy reporting descriptors that are applicable to the result.", - "type": "array", - "default": [], - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/reportingDescriptorReference" - } - }, - - "webRequest": { - "description": "A web request associated with this result.", - "$ref": "#/definitions/webRequest" - }, - - "webResponse": { - "description": "A web response associated with this result.", - "$ref": "#/definitions/webResponse" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the result.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "message" ] - }, - - "resultProvenance": { - "description": "Contains information about how and when a result was detected.", - "additionalProperties": false, - "type": "object", - "properties": { - - "firstDetectionTimeUtc": { - "description": "The Coordinated Universal Time (UTC) date and time at which the result was first detected. See \"Date/time properties\" in the SARIF spec for the required format.", - "type": "string", - "format": "date-time" - }, - - "lastDetectionTimeUtc": { - "description": "The Coordinated Universal Time (UTC) date and time at which the result was most recently detected. See \"Date/time properties\" in the SARIF spec for the required format.", - "type": "string", - "format": "date-time" - }, - - "firstDetectionRunGuid": { - "description": "A GUID-valued string equal to the automationDetails.guid property of the run in which the result was first detected.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "lastDetectionRunGuid": { - "description": "A GUID-valued string equal to the automationDetails.guid property of the run in which the result was most recently detected.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "invocationIndex": { - "description": "The index within the run.invocations array of the invocation object which describes the tool invocation that detected the result.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "conversionSources": { - "description": "An array of physicalLocation objects which specify the portions of an analysis tool's output that a converter transformed into the result.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/physicalLocation" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the result.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "run": { - "description": "Describes a single run of an analysis tool, and contains the reported output of that run.", - "additionalProperties": false, - "type": "object", - "properties": { - - "tool": { - "description": "Information about the tool or tool pipeline that generated the results in this run. A run can only contain results produced by a single tool or tool pipeline. A run can aggregate results from multiple log files, as long as context around the tool run (tool command-line arguments and the like) is identical for all aggregated files.", - "$ref": "#/definitions/tool" - }, - - "invocations": { - "description": "Describes the invocation of the analysis tool.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/invocation" - } - }, - - "conversion": { - "description": "A conversion object that describes how a converter transformed an analysis tool's native reporting format into the SARIF format.", - "$ref": "#/definitions/conversion" - }, - - "language": { - "description": "The language of the messages emitted into the log file during this run (expressed as an ISO 639-1 two-letter lowercase culture code) and an optional region (expressed as an ISO 3166-1 two-letter uppercase subculture code associated with a country or region). The casing is recommended but not required (in order for this data to conform to RFC5646).", - "type": "string", - "default": "en-US", - "pattern": "^[a-zA-Z]{2}|^[a-zA-Z]{2}-[a-zA-Z]{2}]?$" - }, - - "versionControlProvenance": { - "description": "Specifies the revision in version control of the artifacts that were scanned.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/versionControlDetails" - } - }, - - "originalUriBaseIds": { - "description": "The artifact location specified by each uriBaseId symbol on the machine where the tool originally ran.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/artifactLocation" - } - }, - - "artifacts": { - "description": "An array of artifact objects relevant to the run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/artifact" - } - }, - - "logicalLocations": { - "description": "An array of logical locations such as namespaces, types or functions.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/logicalLocation" - } - }, - - "graphs": { - "description": "An array of zero or more unique graph objects associated with the run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/graph" - } - }, - - "results": { - "description": "The set of results contained in an SARIF log. The results array can be omitted when a run is solely exporting rules metadata. It must be present (but may be empty) if a log file represents an actual scan.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "items": { - "$ref": "#/definitions/result" - } - }, - - "automationDetails": { - "description": "Automation details that describe this run.", - "$ref": "#/definitions/runAutomationDetails" - }, - - "runAggregates": { - "description": "Automation details that describe the aggregate of runs to which this run belongs.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/runAutomationDetails" - } - }, - - "baselineGuid": { - "description": "The 'guid' property of a previous SARIF 'run' that comprises the baseline that was used to compute result 'baselineState' properties for the run.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "redactionTokens": { - "description": "An array of strings used to replace sensitive information in a redaction-aware property.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "type": "string" - } - }, - - "defaultEncoding": { - "description": "Specifies the default encoding for any artifact object that refers to a text file.", - "type": "string" - }, - - "defaultSourceLanguage": { - "description": "Specifies the default source language for any artifact object that refers to a text file that contains source code.", - "type": "string" - }, - - "newlineSequences": { - "description": "An ordered list of character sequences that were treated as line breaks when computing region information for the run.", - "type": "array", - "minItems": 1, - "uniqueItems": true, - "default": [ "\r\n", "\n" ], - "items": { - "type": "string" - } - }, - - "columnKind": { - "description": "Specifies the unit in which the tool measures columns.", - "enum": [ "utf16CodeUnits", "unicodeCodePoints" ] - }, - - "externalPropertyFileReferences": { - "description": "References to external property files that should be inlined with the content of a root log file.", - "$ref": "#/definitions/externalPropertyFileReferences" - }, - - "threadFlowLocations": { - "description": "An array of threadFlowLocation objects cached at run level.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/threadFlowLocation" - } - }, - - "taxonomies": { - "description": "An array of toolComponent objects relevant to a taxonomy in which results are categorized.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponent" - } - }, - - "addresses": { - "description": "Addresses associated with this run instance, if any.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "$ref": "#/definitions/address" - } - }, - - "translations": { - "description": "The set of available translations of the localized data provided by the tool.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponent" - } - }, - - "policies": { - "description": "Contains configurations that may potentially override both reportingDescriptor.defaultConfiguration (the tool's default severities) and invocation.configurationOverrides (severities established at run-time from the command line).", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponent" - } - }, - - "webRequests": { - "description": "An array of request objects cached at run level.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/webRequest" - } - }, - - "webResponses": { - "description": "An array of response objects cached at run level.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/webResponse" - } - }, - - "specialLocations": { - "description": "A specialLocations object that defines locations of special significance to SARIF consumers.", - "$ref": "#/definitions/specialLocations" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the run.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "tool" ] - }, - - "runAutomationDetails": { - "description": "Information that describes a run's identity and role within an engineering system process.", - "additionalProperties": false, - "type": "object", - "properties": { - - "description": { - "description": "A description of the identity and role played within the engineering system by this object's containing run object.", - "$ref": "#/definitions/message" - }, - - "id": { - "description": "A hierarchical string that uniquely identifies this object's containing run object.", - "type": "string" - }, - - "guid": { - "description": "A stable, unique identifer for this object's containing run object in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "correlationGuid": { - "description": "A stable, unique identifier for the equivalence class of runs to which this object's containing run object belongs in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the run automation details.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "specialLocations": { - "description": "Defines locations of special significance to SARIF consumers.", - "type": "object", - "additionalProperties": false, - "properties": { - - "displayBase": { - "description": "Provides a suggestion to SARIF consumers to display file paths relative to the specified location.", - "$ref": "#/definitions/artifactLocation" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the special locations.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "stack": { - "description": "A call stack that is relevant to a result.", - "additionalProperties": false, - "type": "object", - "properties": { - - "message": { - "description": "A message relevant to this call stack.", - "$ref": "#/definitions/message" - }, - - "frames": { - "description": "An array of stack frames that represents a sequence of calls, rendered in reverse chronological order, that comprise the call stack.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "items": { - "$ref": "#/definitions/stackFrame" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the stack.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "frames" ] - }, - - "stackFrame": { - "description": "A function call within a stack trace.", - "additionalProperties": false, - "type": "object", - "properties": { - - "location": { - "description": "The location to which this stack frame refers.", - "$ref": "#/definitions/location" - }, - - "module": { - "description": "The name of the module that contains the code of this stack frame.", - "type": "string" - }, - - "threadId": { - "description": "The thread identifier of the stack frame.", - "type": "integer" - }, - - "parameters": { - "description": "The parameters of the call that is executing.", - "type": "array", - "minItems": 0, - "uniqueItems": false, - "default": [], - "items": { - "type": "string", - "default": [] - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the stack frame.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "suppression": { - "description": "A suppression that is relevant to a result.", - "additionalProperties": false, - "type": "object", - "properties": { - - "guid": { - "description": "A stable, unique identifer for the suprression in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "kind": { - "description": "A string that indicates where the suppression is persisted.", - "enum": [ - "inSource", - "external" - ] - }, - - "state": { - "description": "A string that indicates the state of the suppression.", - "enum": [ - "accepted", - "underReview", - "rejected" - ] - }, - - "justification": { - "description": "A string representing the justification for the suppression.", - "type": "string" - }, - - "location": { - "description": "Identifies the location associated with the suppression.", - "$ref": "#/definitions/location" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the suppression.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "kind" ] - }, - - "threadFlow": { - "description": "Describes a sequence of code locations that specify a path through a single thread of execution such as an operating system or fiber.", - "type": "object", - "additionalProperties": false, - "properties": { - - "id": { - "description": "An string that uniquely identifies the threadFlow within the codeFlow in which it occurs.", - "type": "string" - }, - - "message": { - "description": "A message relevant to the thread flow.", - "$ref": "#/definitions/message" - }, - - - "initialState": { - "description": "Values of relevant expressions at the start of the thread flow that may change during thread flow execution.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/multiformatMessageString" - } - }, - - "immutableState": { - "description": "Values of relevant expressions at the start of the thread flow that remain constant.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/multiformatMessageString" - } - }, - - "locations": { - "description": "A temporally ordered array of 'threadFlowLocation' objects, each of which describes a location visited by the tool while producing the result.", - "type": "array", - "minItems": 1, - "uniqueItems": false, - "items": { - "$ref": "#/definitions/threadFlowLocation" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the thread flow.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "locations" ] - }, - - "threadFlowLocation": { - "description": "A location visited by an analysis tool while simulating or monitoring the execution of a program.", - "additionalProperties": false, - "type": "object", - "properties": { - - "index": { - "description": "The index within the run threadFlowLocations array.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "location": { - "description": "The code location.", - "$ref": "#/definitions/location" - }, - - "stack": { - "description": "The call stack leading to this location.", - "$ref": "#/definitions/stack" - }, - - "kinds": { - "description": "A set of distinct strings that categorize the thread flow location. Well-known kinds include 'acquire', 'release', 'enter', 'exit', 'call', 'return', 'branch', 'implicit', 'false', 'true', 'caution', 'danger', 'unknown', 'unreachable', 'taint', 'function', 'handler', 'lock', 'memory', 'resource', 'scope' and 'value'.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "type": "string" - } - }, - - "taxa": { - "description": "An array of references to rule or taxonomy reporting descriptors that are applicable to the thread flow location.", - "type": "array", - "default": [], - "minItems": 0, - "uniqueItems": true, - "items": { - "$ref": "#/definitions/reportingDescriptorReference" - } - }, - - "module": { - "description": "The name of the module that contains the code that is executing.", - "type": "string" - }, - - "state": { - "description": "A dictionary, each of whose keys specifies a variable or expression, the associated value of which represents the variable or expression value. For an annotation of kind 'continuation', for example, this dictionary might hold the current assumed values of a set of global variables.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/multiformatMessageString" - } - }, - - "nestingLevel": { - "description": "An integer representing a containment hierarchy within the thread flow.", - "type": "integer", - "minimum": 0 - }, - - "executionOrder": { - "description": "An integer representing the temporal order in which execution reached this location.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "executionTimeUtc": { - "description": "The Coordinated Universal Time (UTC) date and time at which this location was executed.", - "type": "string", - "format": "date-time" - }, - - "importance": { - "description": "Specifies the importance of this location in understanding the code flow in which it occurs. The order from most to least important is \"essential\", \"important\", \"unimportant\". Default: \"important\".", - "enum": [ "important", "essential", "unimportant" ], - "default": "important" - }, - - "webRequest": { - "description": "A web request associated with this thread flow location.", - "$ref": "#/definitions/webRequest" - }, - - "webResponse": { - "description": "A web response associated with this thread flow location.", - "$ref": "#/definitions/webResponse" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the threadflow location.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "tool": { - "description": "The analysis tool that was run.", - "additionalProperties": false, - "type": "object", - "properties": { - - "driver": { - "description": "The analysis tool that was run.", - "$ref": "#/definitions/toolComponent" - }, - - "extensions": { - "description": "Tool extensions that contributed to or reconfigured the analysis tool that was run.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponent" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the tool.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "driver" ] - }, - - "toolComponent": { - "description": "A component, such as a plug-in or the driver, of the analysis tool that was run.", - "additionalProperties": false, - "type": "object", - "properties": { - - "guid": { - "description": "A unique identifer for the tool component in the form of a GUID.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "name": { - "description": "The name of the tool component.", - "type": "string" - }, - - "organization": { - "description": "The organization or company that produced the tool component.", - "type": "string" - }, - - "product": { - "description": "A product suite to which the tool component belongs.", - "type": "string" - }, - - "productSuite": { - "description": "A localizable string containing the name of the suite of products to which the tool component belongs.", - "type": "string" - }, - - "shortDescription": { - "description": "A brief description of the tool component.", - "$ref": "#/definitions/multiformatMessageString" - }, - - "fullDescription": { - "description": "A comprehensive description of the tool component.", - "$ref": "#/definitions/multiformatMessageString" - }, - - "fullName": { - "description": "The name of the tool component along with its version and any other useful identifying information, such as its locale.", - "type": "string" - }, - - "version": { - "description": "The tool component version, in whatever format the component natively provides.", - "type": "string" - }, - - "semanticVersion": { - "description": "The tool component version in the format specified by Semantic Versioning 2.0.", - "type": "string" - }, - - "dottedQuadFileVersion": { - "description": "The binary version of the tool component's primary executable file expressed as four non-negative integers separated by a period (for operating systems that express file versions in this way).", - "type": "string", - "pattern": "[0-9]+(\\.[0-9]+){3}" - }, - - "releaseDateUtc": { - "description": "A string specifying the UTC date (and optionally, the time) of the component's release.", - "type": "string" - }, - - "downloadUri": { - "description": "The absolute URI from which the tool component can be downloaded.", - "type": "string", - "format": "uri" - }, - - "informationUri": { - "description": "The absolute URI at which information about this version of the tool component can be found.", - "type": "string", - "format": "uri" - }, - - "globalMessageStrings": { - "description": "A dictionary, each of whose keys is a resource identifier and each of whose values is a multiformatMessageString object, which holds message strings in plain text and (optionally) Markdown format. The strings can include placeholders, which can be used to construct a message in combination with an arbitrary number of additional string arguments.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/multiformatMessageString" - } - }, - - "notifications": { - "description": "An array of reportingDescriptor objects relevant to the notifications related to the configuration and runtime execution of the tool component.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/reportingDescriptor" - } - }, - - "rules": { - "description": "An array of reportingDescriptor objects relevant to the analysis performed by the tool component.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/reportingDescriptor" - } - }, - - "taxa": { - "description": "An array of reportingDescriptor objects relevant to the definitions of both standalone and tool-defined taxonomies.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/reportingDescriptor" - } - }, - - "locations": { - "description": "An array of the artifactLocation objects associated with the tool component.", - "type": "array", - "minItems": 0, - "default": [], - "items": { - "$ref": "#/definitions/artifactLocation" - } - }, - - "language": { - "description": "The language of the messages emitted into the log file during this run (expressed as an ISO 639-1 two-letter lowercase language code) and an optional region (expressed as an ISO 3166-1 two-letter uppercase subculture code associated with a country or region). The casing is recommended but not required (in order for this data to conform to RFC5646).", - "type": "string", - "default": "en-US", - "pattern": "^[a-zA-Z]{2}|^[a-zA-Z]{2}-[a-zA-Z]{2}]?$" - }, - - "contents": { - "description": "The kinds of data contained in this object.", - "type": "array", - "uniqueItems": true, - "default": [ "localizedData", "nonLocalizedData" ], - "items": { - "enum": [ - "localizedData", - "nonLocalizedData" - ] - } - }, - - "isComprehensive": { - "description": "Specifies whether this object contains a complete definition of the localizable and/or non-localizable data for this component, as opposed to including only data that is relevant to the results persisted to this log file.", - "type": "boolean", - "default": false - }, - - "localizedDataSemanticVersion": { - "description": "The semantic version of the localized strings defined in this component; maintained by components that provide translations.", - "type": "string" - }, - - "minimumRequiredLocalizedDataSemanticVersion": { - "description": "The minimum value of localizedDataSemanticVersion required in translations consumed by this component; used by components that consume translations.", - "type": "string" - }, - - "associatedComponent": { - "description": "The component which is strongly associated with this component. For a translation, this refers to the component which has been translated. For an extension, this is the driver that provides the extension's plugin model.", - "$ref": "#/definitions/toolComponentReference" - }, - - "translationMetadata": { - "description": "Translation metadata, required for a translation, not populated by other component types.", - "$ref": "#/definitions/translationMetadata" - }, - - "supportedTaxonomies": { - "description": "An array of toolComponentReference objects to declare the taxonomies supported by the tool component.", - "type": "array", - "minItems": 0, - "uniqueItems": true, - "default": [], - "items": { - "$ref": "#/definitions/toolComponentReference" - } - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the tool component.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "name" ] - }, - - "toolComponentReference": { - "description": "Identifies a particular toolComponent object, either the driver or an extension.", - "type": "object", - "additionalProperties": false, - "properties": { - - "name": { - "description": "The 'name' property of the referenced toolComponent.", - "type": "string" - }, - - "index": { - "description": "An index into the referenced toolComponent in tool.extensions.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "guid": { - "description": "The 'guid' property of the referenced toolComponent.", - "type": "string", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the toolComponentReference.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "translationMetadata": { - "description": "Provides additional metadata related to translation.", - "type": "object", - "additionalProperties": false, - "properties": { - - "name": { - "description": "The name associated with the translation metadata.", - "type": "string" - }, - - "fullName": { - "description": "The full name associated with the translation metadata.", - "type": "string" - }, - - "shortDescription": { - "description": "A brief description of the translation metadata.", - "$ref": "#/definitions/multiformatMessageString" - }, - - "fullDescription": { - "description": "A comprehensive description of the translation metadata.", - "$ref": "#/definitions/multiformatMessageString" - }, - - "downloadUri": { - "description": "The absolute URI from which the translation metadata can be downloaded.", - "type": "string", - "format": "uri" - }, - - "informationUri": { - "description": "The absolute URI from which information related to the translation metadata can be downloaded.", - "type": "string", - "format": "uri" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the translation metadata.", - "$ref": "#/definitions/propertyBag" - } - }, - "required": [ "name" ] - }, - - "versionControlDetails": { - "description": "Specifies the information necessary to retrieve a desired revision from a version control system.", - "type": "object", - "additionalProperties": false, - "properties": { - - "repositoryUri": { - "description": "The absolute URI of the repository.", - "type": "string", - "format": "uri" - }, - - "revisionId": { - "description": "A string that uniquely and permanently identifies the revision within the repository.", - "type": "string" - }, - - "branch": { - "description": "The name of a branch containing the revision.", - "type": "string" - }, - - "revisionTag": { - "description": "A tag that has been applied to the revision.", - "type": "string" - }, - - "asOfTimeUtc": { - "description": "A Coordinated Universal Time (UTC) date and time that can be used to synchronize an enlistment to the state of the repository at that time.", - "type": "string", - "format": "date-time" - }, - - "mappedTo": { - "description": "The location in the local file system to which the root of the repository was mapped at the time of the analysis.", - "$ref": "#/definitions/artifactLocation" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the version control details.", - "$ref": "#/definitions/propertyBag" - } - }, - - "required": [ "repositoryUri" ] - }, - - "webRequest": { - "description": "Describes an HTTP request.", - "type": "object", - "additionalProperties": false, - "properties": { - - "index": { - "description": "The index within the run.webRequests array of the request object associated with this result.", - "type": "integer", - "default": -1, - "minimum": -1 - - }, - - "protocol": { - "description": "The request protocol. Example: 'http'.", - "type": "string" - }, - - "version": { - "description": "The request version. Example: '1.1'.", - "type": "string" - }, - - "target": { - "description": "The target of the request.", - "type": "string" - }, - - "method": { - "description": "The HTTP method. Well-known values are 'GET', 'PUT', 'POST', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT'.", - "type": "string" - }, - - "headers": { - "description": "The request headers.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - - "parameters": { - "description": "The request parameters.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - - "body": { - "description": "The body of the request.", - "$ref": "#/definitions/artifactContent" - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the request.", - "$ref": "#/definitions/propertyBag" - } - } - }, - - "webResponse": { - "description": "Describes the response to an HTTP request.", - "type": "object", - "additionalProperties": false, - "properties": { - - "index": { - "description": "The index within the run.webResponses array of the response object associated with this result.", - "type": "integer", - "default": -1, - "minimum": -1 - }, - - "protocol": { - "description": "The response protocol. Example: 'http'.", - "type": "string" - }, - - "version": { - "description": "The response version. Example: '1.1'.", - "type": "string" - }, - - "statusCode": { - "description": "The response status code. Example: 451.", - "type": "integer" - }, - - "reasonPhrase": { - "description": "The response reason. Example: 'Not found'.", - "type": "string" - }, - - "headers": { - "description": "The response headers.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - - "body": { - "description": "The body of the response.", - "$ref": "#/definitions/artifactContent" - }, - - "noResponseReceived": { - "description": "Specifies whether a response was received from the server.", - "type": "boolean", - "default": false - }, - - "properties": { - "description": "Key/value pairs that provide additional information about the response.", - "$ref": "#/definitions/propertyBag" - } - } - } - } -} diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go index 0d2898ff..71dec547 100644 --- a/internal/sarif/sarif.go +++ b/internal/sarif/sarif.go @@ -2,11 +2,8 @@ package sarif import ( "bytes" - "context" - "encoding/json" "fmt" "io" - "io/ioutil" "strings" "github.com/Shopify/kubeaudit" @@ -24,8 +21,7 @@ import ( "github.com/Shopify/kubeaudit/auditors/rootfs" "github.com/Shopify/kubeaudit/auditors/seccomp" "github.com/owenrumney/go-sarif/v2/sarif" - "github.com/qri-io/jsonschema" - "github.com/sirupsen/logrus" + "github.com/xeipuuv/gojsonschema" ) var Auditors = map[string]string{ @@ -80,13 +76,15 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { run.AddRule(result.Rule). WithName(result.Auditor). WithMarkdownHelp(helpMessage). + WithHelp(&sarif.MultiformatMessageString{Text: &docsURL}). + WithShortDescription(&sarif.MultiformatMessageString{Text: &result.Rule}). WithProperties(sarif.Properties{ "tags": []string{ "security", "kubernetes", "infrastructure", }, - "precision": "very-high", // TODO: can we remove this? + "precision": "very-high", }) // SARIF specifies the following severity levels: warning, error, note and none @@ -106,7 +104,6 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { run.AddResult(result) } - // todo: remove this after trying the library var reportBytes bytes.Buffer err = report.Write(&reportBytes) @@ -120,43 +117,32 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { } if len(errs) > 0 { - for _, errorMsg := range errs { - // not sure if we want to return the errors here - // so just logging them for now - logrus.Info(errorMsg) - } + return nil, fmt.Errorf("SARIF schema validation errors: %s", errs) } - // todo: remove validate - return report, nil } // Validates that the SARIF file is valid as per sarif spec // https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Documents/CommitteeSpecifications/2.1.0/sarif-schema-2.1.0.json -func validate(report io.Reader) (error, []jsonschema.KeyError) { - schemaData, err := ioutil.ReadFile("sarif-schema.json") - if err != nil { - return err, nil - } - - jsonSchema := &jsonschema.Schema{} - - if err := json.Unmarshal(schemaData, jsonSchema); err != nil { - return err, nil - } +func validate(report io.Reader) (error, []gojsonschema.ResultError) { + schemaLoader := gojsonschema.NewReferenceLoader("http://json.schemastore.org/sarif-2.1.0") + var reportLoader gojsonschema.JSONLoader _, ok := report.(*bytes.Buffer) if ok { - errs, err := jsonSchema.ValidateBytes(context.Background(), report.(*bytes.Buffer).Bytes()) - if err != nil { - return err, nil - } + reportLoader = gojsonschema.NewStringLoader(report.(*bytes.Buffer).String()) + } - if len(errs) > 0 { - return nil, errs - } + result, err := gojsonschema.Validate(schemaLoader, reportLoader) + + if err != nil { + panic(err.Error()) } - return nil, nil + if result.Valid() { + return nil, nil + } else { + return nil, result.Errors() + } } diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index 13884e0f..99910ea4 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -2,7 +2,6 @@ package sarif import ( "bytes" - "fmt" "io/ioutil" "os" "path/filepath" @@ -109,18 +108,36 @@ func TestCreate(t *testing.T) { } func TestValidate(t *testing.T) { - var reportBytes bytes.Buffer - testSarif, err := ioutil.ReadFile("fixtures/valid.sarif") - require.NoError(t, err) - reportBytes.Write(testSarif) + cases := []struct { + file string + shouldBeValid bool + }{ + { + file: "invalid.sarif", + shouldBeValid: false, + }, + { + file: "valid.sarif", + shouldBeValid: true, + }, + } - err, errs := validate(&reportBytes) - require.NoError(t, err) + for _, tc := range cases { + var reportBytes bytes.Buffer - if len(errs) > 0 { - fmt.Println(errs) - } + testSarif, err := ioutil.ReadFile("fixtures/" + tc.file) + require.NoError(t, err) + + reportBytes.Write(testSarif) - assert.Len(t, errs, 0) + err, errs := validate(&reportBytes) + require.NoError(t, err) + + if !tc.shouldBeValid { + assert.True(t, len(errs) > 0) + } else { + assert.Len(t, errs, 0) + } + } } diff --git a/kubeaudit.sarif b/kubeaudit.sarif new file mode 100644 index 00000000..0bf5ce29 --- /dev/null +++ b/kubeaudit.sarif @@ -0,0 +1,96 @@ +{ + "version": "2.1.0", + "$schema": "https://json.schemastore.org/sarif-2.1.0-rtm.5.json", + "runs": [ + { + "tool": { + "driver": { + "informationUri": "https://github.com/Shopify/kubeaudit", + "name": "kubeaudit", + "rules": [ + { + "id": "AppArmorInvalidAnnotation", + "name": "apparmor", + "shortDescription": { + "text": "AppArmorInvalidAnnotation" + }, + "help": { + "text": "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/apparmor.md" + }, + "properties": { + "precision": "very-high", + "tags": [ + "security", + "kubernetes", + "infrastructure" + ] + } + }, + { + "id": "AutomountServiceAccountTokenTrueAndDefaultSA", + "name": "asat", + "shortDescription": { + "text": "AutomountServiceAccountTokenTrueAndDefaultSA" + }, + "help": { + "text": "" + }, + "properties": { + "precision": "very-high", + "tags": [ + "security", + "kubernetes", + "infrastructure" + ] + } + } + ] + } + }, + "results": [ + { + "ruleId": "AppArmorInvalidAnnotation", + "ruleIndex": 0, + "level": "error", + "message": { + "text": "AppArmor annotation key refers to a container that doesn't exist. Remove the annotation 'container.apparmor.security.beta.kubernetes.io/container: badval'." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/sarif/fixtures/apparmor-disabled.yaml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + }, + { + "ruleId": "AutomountServiceAccountTokenTrueAndDefaultSA", + "ruleIndex": 1, + "level": "error", + "message": { + "text": "Default service account with token mounted. automountServiceAccountToken should be set to 'false' on either the ServiceAccount or on the PodSpec or a non-default service account should be used." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "internal/sarif/fixtures/apparmor-disabled.yaml", + "uriBaseId": "ROOTPATH" + }, + "region": { + "startLine": 1 + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file From f18847a79f26b045681434b0cc802c3172ae35d9 Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Wed, 20 Jul 2022 11:59:24 -0400 Subject: [PATCH 12/31] adds more tests and fixes json validation --- ...or-disabled.yaml => apparmor-invalid.yaml} | 0 .../sarif/fixtures/image-tag-present.yaml | 16 ++++++ internal/sarif/sarif.go | 28 +++++----- internal/sarif/sarif_test.go | 55 +++++++++---------- 4 files changed, 58 insertions(+), 41 deletions(-) rename internal/sarif/fixtures/{apparmor-disabled.yaml => apparmor-invalid.yaml} (100%) create mode 100644 internal/sarif/fixtures/image-tag-present.yaml diff --git a/internal/sarif/fixtures/apparmor-disabled.yaml b/internal/sarif/fixtures/apparmor-invalid.yaml similarity index 100% rename from internal/sarif/fixtures/apparmor-disabled.yaml rename to internal/sarif/fixtures/apparmor-invalid.yaml diff --git a/internal/sarif/fixtures/image-tag-present.yaml b/internal/sarif/fixtures/image-tag-present.yaml new file mode 100644 index 00000000..a24e6ac5 --- /dev/null +++ b/internal/sarif/fixtures/image-tag-present.yaml @@ -0,0 +1,16 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment +spec: + selector: + matchLabels: + name: deployment + template: + metadata: + labels: + name: deployment + spec: + containers: + - name: deployment + image: scratch:1.5 diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go index 71dec547..6a8e93f2 100644 --- a/internal/sarif/sarif.go +++ b/internal/sarif/sarif.go @@ -10,6 +10,7 @@ import ( "github.com/Shopify/kubeaudit/auditors/apparmor" "github.com/Shopify/kubeaudit/auditors/asat" "github.com/Shopify/kubeaudit/auditors/capabilities" + "github.com/Shopify/kubeaudit/auditors/deprecatedapis" "github.com/Shopify/kubeaudit/auditors/hostns" "github.com/Shopify/kubeaudit/auditors/image" "github.com/Shopify/kubeaudit/auditors/limits" @@ -25,19 +26,20 @@ import ( ) var Auditors = map[string]string{ - apparmor.Name: "Finds containers that do not have AppArmor enabled", - asat.Name: "Finds containers where the deprecated SA field is used or with a mounted default SA", - capabilities.Name: "Finds containers that do not drop the recommended capabilities or add new ones", - hostns.Name: "Finds containers that have HostPID, HostIPC or HostNetwork enabled", - image.Name: "Finds containers which do not use the desired version of an image (via the tag) or use an image without a tag", - limits.Name: "Finds containers which exceed the specified CPU and memory limits or do not specify any", - mounts.Name: "Finds containers that have sensitive host paths mounted", - netpols.Name: "Finds namespaces that do not have a default-deny network policy", - nonroot.Name: "Finds containers allowed to run as root", - privesc.Name: "Finds containers that allow privilege escalation", - privileged.Name: "Finds containers running as privileged", - rootfs.Name: "Finds containers which do not have a read-only filesystem", - seccomp.Name: "Finds containers running without seccomp", + apparmor.Name: "Finds containers that do not have AppArmor enabled", + asat.Name: "Finds containers where the deprecated SA field is used or with a mounted default SA", + capabilities.Name: "Finds containers that do not drop the recommended capabilities or add new ones", + deprecatedapis.Name: "Finds any resource defined with a deprecated API version.", + hostns.Name: "Finds containers that have HostPID, HostIPC or HostNetwork enabled", + image.Name: "Finds containers which do not use the desired version of an image (via the tag) or use an image without a tag", + limits.Name: "Finds containers which exceed the specified CPU and memory limits or do not specify any", + mounts.Name: "Finds containers that have sensitive host paths mounted", + netpols.Name: "Finds namespaces that do not have a default-deny network policy", + nonroot.Name: "Finds containers allowed to run as root", + privesc.Name: "Finds containers that allow privilege escalation", + privileged.Name: "Finds containers running as privileged", + rootfs.Name: "Finds containers which do not have a read-only filesystem", + seccomp.Name: "Finds containers running without seccomp", } // Create generates new sarif Report or returns an error diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index 99910ea4..415cb677 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -10,7 +10,7 @@ import ( "github.com/Shopify/kubeaudit" "github.com/Shopify/kubeaudit/auditors/apparmor" "github.com/Shopify/kubeaudit/auditors/capabilities" - "github.com/Shopify/kubeaudit/auditors/seccomp" + "github.com/Shopify/kubeaudit/auditors/image" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -18,31 +18,39 @@ import ( func TestCreate(t *testing.T) { capabilitiesAuditable := capabilities.New(capabilities.Config{}) apparmorAuditable := apparmor.New() - seccompAuditable := seccomp.New() + imageAuditable := image.New(image.Config{Image: "scratch:1.5"}) cases := []struct { - file string - auditorName string - auditors []kubeaudit.Auditable - expectedRules []string + file string + auditorName string + auditors []kubeaudit.Auditable + expectedRule string + expectedErrorLevel string + expectedMessage string }{ { - "apparmor-disabled.yaml", + "apparmor-invalid.yaml", apparmor.Name, []kubeaudit.Auditable{apparmorAuditable}, - []string{"AppArmorInvalidAnnotation"}, + apparmor.AppArmorInvalidAnnotation, + "error", + "AppArmor annotation key refers to a container that doesn't exist", }, { "capabilities-added.yaml", capabilities.Name, - []kubeaudit.Auditable{capabilitiesAuditable, seccompAuditable}, - []string{"CapabilityAdded", "SeccompAnnotationMissing"}, + []kubeaudit.Auditable{capabilitiesAuditable}, + capabilities.CapabilityAdded, + "error", + "It should be removed from the capability add list", }, { - "capabilities-added.yaml", + "image-tag-present.yaml", capabilities.Name, - []kubeaudit.Auditable{capabilitiesAuditable}, - []string{"CapabilityAdded"}, + []kubeaudit.Auditable{imageAuditable}, + image.ImageCorrect, + "note", + "Image tag is correct", }, } @@ -67,8 +75,6 @@ func TestCreate(t *testing.T) { } } - // verify that the file path is correct - assert.Contains(t, kubeAuditReport.Results()[0].GetAuditResults()[0].FilePath, "sarif/fixtures") sarifReport, err := Create(kubeAuditReport) require.NoError(t, err) @@ -77,13 +83,11 @@ func TestCreate(t *testing.T) { *sarifReport.Runs[0].Tool.Driver.InformationURI) // verify that the rules have been added as per report findings - assert.Len(t, sarifReport.Runs[0].Tool.Driver.Rules, len(tc.expectedRules)) + assert.Equal(t, sarifReport.Runs[0].Tool.Driver.Rules[0].ID, tc.expectedRule) var ruleNames []string // check for rules occurrences for _, sarifRule := range sarifReport.Runs[0].Tool.Driver.Rules { - assert.NotEqual(t, *sarifRule.Help.Markdown, "") - assert.Equal(t, sarifRule.Properties["tags"], []string{ "security", "kubernetes", @@ -93,17 +97,12 @@ func TestCreate(t *testing.T) { ruleNames = append(ruleNames, sarifRule.ID) } - for _, expectedRule := range tc.expectedRules { - assert.Contains(t, ruleNames, expectedRule) + for _, sarifResult := range sarifReport.Runs[0].Results { + assert.Contains(t, ruleNames, *sarifResult.RuleID) + assert.Equal(t, tc.expectedErrorLevel, *sarifResult.Level) + assert.Contains(t, *sarifResult.Message.Text, tc.expectedMessage) + assert.Equal(t, "sarif/fixtures/"+tc.file, *sarifResult.Locations[0].PhysicalLocation.ArtifactLocation.URI) } - - // for _, sarifResult := range sarifReport.Runs[0].Results { - // // TODO: test for severity, message and location - // // assert.Equal(sarifResult.Level, - // } - - // also add a fixture with info level so that we capture the conversion to note - } } From 05ed3bad3fba97258c92ac51871b7199f8a6d728 Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Wed, 20 Jul 2022 14:25:41 -0400 Subject: [PATCH 13/31] adds violations to rules mapping --- internal/sarif/rules.go | 80 +++++++++++++++++++++++++++++++ internal/sarif/rules_test.go | 92 ++++++++++++++++++++++++++++++++++++ internal/sarif/sarif.go | 38 ++------------- internal/sarif/sarif_test.go | 19 +++----- kubeaudit.go | 8 +++- profile.out | 12 ----- 6 files changed, 190 insertions(+), 59 deletions(-) create mode 100644 internal/sarif/rules.go create mode 100644 internal/sarif/rules_test.go delete mode 100644 profile.out diff --git a/internal/sarif/rules.go b/internal/sarif/rules.go new file mode 100644 index 00000000..e09dc121 --- /dev/null +++ b/internal/sarif/rules.go @@ -0,0 +1,80 @@ +package sarif + +import ( + "github.com/Shopify/kubeaudit/auditors/apparmor" + "github.com/Shopify/kubeaudit/auditors/asat" + "github.com/Shopify/kubeaudit/auditors/capabilities" + "github.com/Shopify/kubeaudit/auditors/deprecatedapis" + "github.com/Shopify/kubeaudit/auditors/hostns" + "github.com/Shopify/kubeaudit/auditors/image" + "github.com/Shopify/kubeaudit/auditors/limits" + "github.com/Shopify/kubeaudit/auditors/mounts" + "github.com/Shopify/kubeaudit/auditors/netpols" + "github.com/Shopify/kubeaudit/auditors/nonroot" + "github.com/Shopify/kubeaudit/auditors/privesc" + "github.com/Shopify/kubeaudit/auditors/privileged" + "github.com/Shopify/kubeaudit/auditors/rootfs" + "github.com/Shopify/kubeaudit/auditors/seccomp" +) + +var allAuditors = map[string]string{ + apparmor.Name: "Finds containers that do not have AppArmor enabled", + asat.Name: "Finds containers where the deprecated SA field is used or with a mounted default SA", + capabilities.Name: "Finds containers that do not drop the recommended capabilities or add new ones", + deprecatedapis.Name: "Finds any resource defined with a deprecated API version.", + hostns.Name: "Finds containers that have HostPID, HostIPC or HostNetwork enabled", + image.Name: "Finds containers which do not use the desired version of an image (via the tag) or use an image without a tag", + limits.Name: "Finds containers which exceed the specified CPU and memory limits or do not specify any", + mounts.Name: "Finds containers that have sensitive host paths mounted", + netpols.Name: "Finds namespaces that do not have a default-deny network policy", + nonroot.Name: "Finds containers allowed to run as root", + privesc.Name: "Finds containers that allow privilege escalation", + privileged.Name: "Finds containers running as privileged", + rootfs.Name: "Finds containers which do not have a read-only filesystem", + seccomp.Name: "Finds containers running without seccomp", +} + +var violationsToRules = map[string]string{ + apparmor.AppArmorAnnotationMissing: apparmor.Name, + apparmor.AppArmorDisabled: apparmor.Name, + apparmor.AppArmorInvalidAnnotation: apparmor.Name, + asat.AutomountServiceAccountTokenDeprecated: asat.Name, + asat.AutomountServiceAccountTokenTrueAndDefaultSA: asat.Name, + capabilities.CapabilityAdded: capabilities.Name, + capabilities.CapabilityOrSecurityContextMissing: capabilities.Name, + capabilities.CapabilityShouldDropAll: capabilities.Name, + deprecatedapis.DeprecatedAPIUsed: deprecatedapis.Name, + hostns.NamespaceHostIPCTrue: hostns.Name, + hostns.NamespaceHostNetworkTrue: hostns.Name, + hostns.NamespaceHostPIDTrue: hostns.Name, + image.ImageCorrect: image.Name, + image.ImageTagIncorrect: image.Name, + image.ImageTagMissing: image.Name, + limits.LimitsCPUExceeded: limits.Name, + limits.LimitsCPUNotSet: limits.Name, + limits.LimitsMemoryExceeded: limits.Name, + limits.LimitsMemoryNotSet: limits.Name, + limits.LimitsNotSet: limits.Name, + mounts.SensitivePathsMounted: mounts.Name, + netpols.MissingDefaultDenyIngressAndEgressNetworkPolicy: netpols.Name, + netpols.MissingDefaultDenyIngressNetworkPolicy: netpols.Name, + netpols.MissingDefaultDenyEgressNetworkPolicy: netpols.Name, + netpols.AllowAllIngressNetworkPolicyExists: netpols.Name, + netpols.AllowAllEgressNetworkPolicyExists: netpols.Name, + nonroot.RunAsUserCSCRoot: nonroot.Name, + nonroot.RunAsUserPSCRoot: nonroot.Name, + nonroot.RunAsNonRootCSCFalse: nonroot.Name, + nonroot.RunAsNonRootPSCNilCSCNil: nonroot.Name, + nonroot.RunAsNonRootPSCFalseCSCNil: nonroot.Name, + privesc.AllowPrivilegeEscalationNil: privesc.Name, + privesc.AllowPrivilegeEscalationTrue: privesc.Name, + privileged.PrivilegedTrue: privileged.Name, + privileged.PrivilegedNil: privileged.Name, + rootfs.ReadOnlyRootFilesystemFalse: rootfs.Name, + rootfs.ReadOnlyRootFilesystemNil: rootfs.Name, + seccomp.SeccompAnnotationMissing: seccomp.Name, + seccomp.SeccompDeprecatedPod: seccomp.Name, + seccomp.SeccompDisabledPod: seccomp.Name, + seccomp.SeccompDeprecatedContainer: seccomp.Name, + seccomp.SeccompDisabledContainer: seccomp.Name, +} diff --git a/internal/sarif/rules_test.go b/internal/sarif/rules_test.go new file mode 100644 index 00000000..4eba5371 --- /dev/null +++ b/internal/sarif/rules_test.go @@ -0,0 +1,92 @@ +package sarif + +import ( + "testing" + + "github.com/Shopify/kubeaudit/auditors/all" + "github.com/stretchr/testify/assert" +) + +func TestAuditorsLength(t *testing.T) { + // if new auditors are created + // make sure they're added with a matching description + assert.Len(t, allAuditors, len(all.AuditorNames)) +} + +func TestViolationToRules(t *testing.T) { + cases := []struct { + auditorName string + expectedCount int + }{ + { + "apparmor", + 3, + }, + { + "asat", + 2, + }, + { + "capabilities", + 3, + }, + { + "deprecatedapis", + 1, + }, + { + "hostns", + 3, + }, + { + "image", + 3, + }, + { + "limits", + 5, + }, + { + "mounts", + 1, + }, + { + "netpols", + 5, + }, + { + "nonroot", + 5, + }, + { + "privesc", + 2, + }, + { + "privileged", + 2, + }, + { + "rootfs", + 2, + }, + { + "seccomp", + 5, + }, + } + + assert.Len(t, cases, len(all.AuditorNames)) + + for _, c := range cases { + var totalCount int + + for _, v := range violationsToRules { + if v == c.auditorName { + totalCount += 1 + } + } + + assert.Equal(t, c.expectedCount, totalCount) + } +} diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go index 6a8e93f2..d4fcb9c3 100644 --- a/internal/sarif/sarif.go +++ b/internal/sarif/sarif.go @@ -7,41 +7,10 @@ import ( "strings" "github.com/Shopify/kubeaudit" - "github.com/Shopify/kubeaudit/auditors/apparmor" - "github.com/Shopify/kubeaudit/auditors/asat" - "github.com/Shopify/kubeaudit/auditors/capabilities" - "github.com/Shopify/kubeaudit/auditors/deprecatedapis" - "github.com/Shopify/kubeaudit/auditors/hostns" - "github.com/Shopify/kubeaudit/auditors/image" - "github.com/Shopify/kubeaudit/auditors/limits" - "github.com/Shopify/kubeaudit/auditors/mounts" - "github.com/Shopify/kubeaudit/auditors/netpols" - "github.com/Shopify/kubeaudit/auditors/nonroot" - "github.com/Shopify/kubeaudit/auditors/privesc" - "github.com/Shopify/kubeaudit/auditors/privileged" - "github.com/Shopify/kubeaudit/auditors/rootfs" - "github.com/Shopify/kubeaudit/auditors/seccomp" "github.com/owenrumney/go-sarif/v2/sarif" "github.com/xeipuuv/gojsonschema" ) -var Auditors = map[string]string{ - apparmor.Name: "Finds containers that do not have AppArmor enabled", - asat.Name: "Finds containers where the deprecated SA field is used or with a mounted default SA", - capabilities.Name: "Finds containers that do not drop the recommended capabilities or add new ones", - deprecatedapis.Name: "Finds any resource defined with a deprecated API version.", - hostns.Name: "Finds containers that have HostPID, HostIPC or HostNetwork enabled", - image.Name: "Finds containers which do not use the desired version of an image (via the tag) or use an image without a tag", - limits.Name: "Finds containers which exceed the specified CPU and memory limits or do not specify any", - mounts.Name: "Finds containers that have sensitive host paths mounted", - netpols.Name: "Finds namespaces that do not have a default-deny network policy", - nonroot.Name: "Finds containers allowed to run as root", - privesc.Name: "Finds containers that allow privilege escalation", - privileged.Name: "Finds containers running as privileged", - rootfs.Name: "Finds containers which do not have a read-only filesystem", - seccomp.Name: "Finds containers running without seccomp", -} - // Create generates new sarif Report or returns an error func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { // create a new report object @@ -65,14 +34,15 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { for _, result := range results { severityLevel := result.Severity.String() auditor := strings.ToLower(result.Auditor) - ruleID := strings.ToLower(result.Rule) var docsURL string - if strings.Contains(ruleID, auditor) { + + auditor, ok := violationsToRules[result.Rule] + if ok { docsURL = "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/" + auditor + ".md" } - helpMessage := fmt.Sprintf("**Type**: kubernetes\n**Docs**: %s\n**Description:** %s", docsURL, Auditors[auditor]) + helpMessage := fmt.Sprintf("**Type**: kubernetes\n**Docs**: %s\n**Description:** %s", docsURL, allAuditors[auditor]) // we only add rules to the report based on the result findings run.AddRule(result.Rule). diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index 415cb677..da8fe133 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -27,6 +27,7 @@ func TestCreate(t *testing.T) { expectedRule string expectedErrorLevel string expectedMessage string + expectedURI string }{ { "apparmor-invalid.yaml", @@ -35,6 +36,7 @@ func TestCreate(t *testing.T) { apparmor.AppArmorInvalidAnnotation, "error", "AppArmor annotation key refers to a container that doesn't exist", + "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/apparmor.md", }, { "capabilities-added.yaml", @@ -43,6 +45,7 @@ func TestCreate(t *testing.T) { capabilities.CapabilityAdded, "error", "It should be removed from the capability add list", + "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/capabilities.md", }, { "image-tag-present.yaml", @@ -51,6 +54,7 @@ func TestCreate(t *testing.T) { image.ImageCorrect, "note", "Image tag is correct", + "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/image.md", }, } @@ -65,17 +69,6 @@ func TestCreate(t *testing.T) { kubeAuditReport, err := auditor.AuditManifest(fixture, manifest) require.NoError(t, err) - // we're only appending sarif to the path here for testing purposes - // this allows us to visualize the sarif output preview correctly - for _, reportResult := range kubeAuditReport.Results() { - r := reportResult.GetAuditResults() - - for _, auditResult := range r { - auditResult.FilePath = filepath.Join("sarif/", auditResult.FilePath) - } - - } - sarifReport, err := Create(kubeAuditReport) require.NoError(t, err) @@ -95,13 +88,15 @@ func TestCreate(t *testing.T) { }) ruleNames = append(ruleNames, sarifRule.ID) + + assert.Equal(t, tc.expectedURI, *sarifRule.Help.Text) } for _, sarifResult := range sarifReport.Runs[0].Results { assert.Contains(t, ruleNames, *sarifResult.RuleID) assert.Equal(t, tc.expectedErrorLevel, *sarifResult.Level) assert.Contains(t, *sarifResult.Message.Text, tc.expectedMessage) - assert.Equal(t, "sarif/fixtures/"+tc.file, *sarifResult.Locations[0].PhysicalLocation.ArtifactLocation.URI) + assert.Contains(t, *sarifResult.Locations[0].PhysicalLocation.ArtifactLocation.URI, "sarif/fixtures/"+tc.file) } } } diff --git a/kubeaudit.go b/kubeaudit.go index b1fd5d0b..91da0324 100644 --- a/kubeaudit.go +++ b/kubeaudit.go @@ -108,6 +108,7 @@ import ( "fmt" "io" "io/ioutil" + "path/filepath" "github.com/Shopify/kubeaudit/internal/k8sinternal" "github.com/Shopify/kubeaudit/pkg/k8s" @@ -157,7 +158,12 @@ func (a *Kubeaudit) AuditManifest(manifestPath string, manifest io.Reader) (*Rep for _, result := range results { auditResults := result.GetAuditResults() for _, ar := range auditResults { - ar.FilePath = manifestPath + path, err := filepath.Abs(manifestPath) + if err != nil { + return nil, err + } + + ar.FilePath = path } } diff --git a/profile.out b/profile.out deleted file mode 100644 index e7cd5134..00000000 --- a/profile.out +++ /dev/null @@ -1,12 +0,0 @@ -mode: atomic -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:41.47,44.16 2 4 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:49.2,53.25 3 4 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:44.16,46.3 1 0 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:57.64,60.57 2 3 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:65.2,65.33 1 3 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:60.57,63.3 2 3 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:65.33,71.40 5 6 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:75.3,93.41 3 6 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:97.3,104.24 3 6 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:71.40,73.4 1 2 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:93.41,95.4 1 0 From f845c3e56537d9cdd7ac0bff1df8424b53f0f355 Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Wed, 20 Jul 2022 15:09:17 -0400 Subject: [PATCH 14/31] refactors filepath test --- internal/sarif/sarif_test.go | 2 +- kubeaudit.go | 13 ++--- kubeaudit.sarif | 96 ------------------------------------ 3 files changed, 8 insertions(+), 103 deletions(-) delete mode 100644 kubeaudit.sarif diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index da8fe133..7d91e872 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -96,7 +96,7 @@ func TestCreate(t *testing.T) { assert.Contains(t, ruleNames, *sarifResult.RuleID) assert.Equal(t, tc.expectedErrorLevel, *sarifResult.Level) assert.Contains(t, *sarifResult.Message.Text, tc.expectedMessage) - assert.Contains(t, *sarifResult.Locations[0].PhysicalLocation.ArtifactLocation.URI, "sarif/fixtures/"+tc.file) + assert.Contains(t, "sarif/fixtures/"+tc.file, *sarifResult.Locations[0].PhysicalLocation.ArtifactLocation.URI) } } } diff --git a/kubeaudit.go b/kubeaudit.go index 91da0324..bc2a57e6 100644 --- a/kubeaudit.go +++ b/kubeaudit.go @@ -109,6 +109,7 @@ import ( "io" "io/ioutil" "path/filepath" + "strings" "github.com/Shopify/kubeaudit/internal/k8sinternal" "github.com/Shopify/kubeaudit/pkg/k8s" @@ -157,13 +158,13 @@ func (a *Kubeaudit) AuditManifest(manifestPath string, manifest io.Reader) (*Rep for _, result := range results { auditResults := result.GetAuditResults() - for _, ar := range auditResults { - path, err := filepath.Abs(manifestPath) - if err != nil { - return nil, err - } - ar.FilePath = path + if !filepath.IsAbs(manifestPath) { + manifestPath = strings.TrimPrefix(filepath.Clean("/"+manifestPath), "/") + } + + for _, ar := range auditResults { + ar.FilePath = manifestPath } } diff --git a/kubeaudit.sarif b/kubeaudit.sarif deleted file mode 100644 index 0bf5ce29..00000000 --- a/kubeaudit.sarif +++ /dev/null @@ -1,96 +0,0 @@ -{ - "version": "2.1.0", - "$schema": "https://json.schemastore.org/sarif-2.1.0-rtm.5.json", - "runs": [ - { - "tool": { - "driver": { - "informationUri": "https://github.com/Shopify/kubeaudit", - "name": "kubeaudit", - "rules": [ - { - "id": "AppArmorInvalidAnnotation", - "name": "apparmor", - "shortDescription": { - "text": "AppArmorInvalidAnnotation" - }, - "help": { - "text": "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/apparmor.md" - }, - "properties": { - "precision": "very-high", - "tags": [ - "security", - "kubernetes", - "infrastructure" - ] - } - }, - { - "id": "AutomountServiceAccountTokenTrueAndDefaultSA", - "name": "asat", - "shortDescription": { - "text": "AutomountServiceAccountTokenTrueAndDefaultSA" - }, - "help": { - "text": "" - }, - "properties": { - "precision": "very-high", - "tags": [ - "security", - "kubernetes", - "infrastructure" - ] - } - } - ] - } - }, - "results": [ - { - "ruleId": "AppArmorInvalidAnnotation", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "AppArmor annotation key refers to a container that doesn't exist. Remove the annotation 'container.apparmor.security.beta.kubernetes.io/container: badval'." - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/sarif/fixtures/apparmor-disabled.yaml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "AutomountServiceAccountTokenTrueAndDefaultSA", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Default service account with token mounted. automountServiceAccountToken should be set to 'false' on either the ServiceAccount or on the PodSpec or a non-default service account should be used." - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/sarif/fixtures/apparmor-disabled.yaml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - } - ] - } - ] -} \ No newline at end of file From 6bfc66be3b6e1e06bb4d91832578e3c7991865ed Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Wed, 20 Jul 2022 17:53:27 -0400 Subject: [PATCH 15/31] adds more info on what kubeaudit does --- internal/sarif/fixtures/valid.sarif | 1319 +-------------------------- internal/sarif/sarif.go | 10 +- internal/sarif/sarif_test.go | 2 +- 3 files changed, 29 insertions(+), 1302 deletions(-) diff --git a/internal/sarif/fixtures/valid.sarif b/internal/sarif/fixtures/valid.sarif index 6ead9064..2328dd73 100644 --- a/internal/sarif/fixtures/valid.sarif +++ b/internal/sarif/fixtures/valid.sarif @@ -5,351 +5,61 @@ { "tool": { "driver": { - "fullName": "Trivy Vulnerability Scanner", - "informationUri": "https://github.com/aquasecurity/trivy", - "name": "Trivy", + "informationUri": "https://github.com/Shopify/kubeaudit", + "name": "kubeaudit", "rules": [ { - "id": "KSV008", - "name": "Misconfiguration", + "id": "AppArmorInvalidAnnotation", + "name": "apparmor", "shortDescription": { - "text": "KSV008" + "text": "AppArmorInvalidAnnotation" }, - "fullDescription": { - "text": "Sharing the host’s IPC namespace allows container processes to communicate with processes on the host." - }, - "defaultConfiguration": { - "level": "error" - }, - "helpUri": "https://avd.aquasec.com/appshield/ksv008", - "help": { - "text": "Misconfiguration KSV008\nType: Kubernetes Security Check\nSeverity: HIGH\nCheck: Access to host IPC namespace\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)\nSharing the host’s IPC namespace allows container processes to communicate with processes on the host.", - "markdown": "**Misconfiguration KSV008**\n| Type | Severity | Check | Message | Link |\n| --- | --- | --- | --- | --- |\n|Kubernetes Security Check|HIGH|Access to host IPC namespace|StatefulSet 'statefulset' should not set 'spec.template.spec.hostIPC' to true|[KSV008](https://avd.aquasec.com/appshield/ksv008)|\n\nSharing the host’s IPC namespace allows container processes to communicate with processes on the host." - }, - "properties": { - "precision": "very-high", - "security-severity": "8.0", - "tags": [ - "misconfiguration", - "security", - "HIGH" - ] - } - }, - { - "id": "KSV009", - "name": "Misconfiguration", - "shortDescription": { - "text": "KSV009" - }, - "fullDescription": { - "text": "Sharing the host’s network namespace permits processes in the pod to communicate with processes bound to the host’s loopback adapter." - }, - "defaultConfiguration": { - "level": "error" - }, - "helpUri": "https://avd.aquasec.com/appshield/ksv009", - "help": { - "text": "Misconfiguration KSV009\nType: Kubernetes Security Check\nSeverity: HIGH\nCheck: Access to host network\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)\nSharing the host’s network namespace permits processes in the pod to communicate with processes bound to the host’s loopback adapter.", - "markdown": "**Misconfiguration KSV009**\n| Type | Severity | Check | Message | Link |\n| --- | --- | --- | --- | --- |\n|Kubernetes Security Check|HIGH|Access to host network|StatefulSet 'statefulset' should not set 'spec.template.spec.hostNetwork' to true|[KSV009](https://avd.aquasec.com/appshield/ksv009)|\n\nSharing the host’s network namespace permits processes in the pod to communicate with processes bound to the host’s loopback adapter." - }, - "properties": { - "precision": "very-high", - "security-severity": "8.0", - "tags": [ - "misconfiguration", - "security", - "HIGH" - ] - } - }, - { - "id": "KSV010", - "name": "Misconfiguration", - "shortDescription": { - "text": "KSV010" - }, - "fullDescription": { - "text": "Sharing the host’s PID namespace allows visibility on host processes, potentially leaking information such as environment variables and configuration." - }, - "defaultConfiguration": { - "level": "error" - }, - "helpUri": "https://avd.aquasec.com/appshield/ksv010", - "help": { - "text": "Misconfiguration KSV010\nType: Kubernetes Security Check\nSeverity: HIGH\nCheck: Access to host PID\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)\nSharing the host’s PID namespace allows visibility on host processes, potentially leaking information such as environment variables and configuration.", - "markdown": "**Misconfiguration KSV010**\n| Type | Severity | Check | Message | Link |\n| --- | --- | --- | --- | --- |\n|Kubernetes Security Check|HIGH|Access to host PID|StatefulSet 'statefulset' should not set 'spec.template.spec.hostPID' to true|[KSV010](https://avd.aquasec.com/appshield/ksv010)|\n\nSharing the host’s PID namespace allows visibility on host processes, potentially leaking information such as environment variables and configuration." - }, - "properties": { - "precision": "very-high", - "security-severity": "8.0", - "tags": [ - "misconfiguration", - "security", - "HIGH" - ] - } - }, - { - "id": "KSV006", - "name": "Misconfiguration", - "shortDescription": { - "text": "KSV006" - }, - "fullDescription": { - "text": "Mounting docker.sock from the host can give the container full root access to the host." - }, - "defaultConfiguration": { - "level": "error" - }, - "helpUri": "https://avd.aquasec.com/appshield/ksv006", "help": { - "text": "Misconfiguration KSV006\nType: Kubernetes Security Check\nSeverity: HIGH\nCheck: hostPath volume mounted with docker.sock\nMessage: Pod 'pod' should not specify '/var/run/docker.socker' in 'spec.template.volumes.hostPath.path'\nLink: [KSV006](https://avd.aquasec.com/appshield/ksv006)\nMounting docker.sock from the host can give the container full root access to the host.", - "markdown": "**Misconfiguration KSV006**\n| Type | Severity | Check | Message | Link |\n| --- | --- | --- | --- | --- |\n|Kubernetes Security Check|HIGH|hostPath volume mounted with docker.sock|Pod 'pod' should not specify '/var/run/docker.socker' in 'spec.template.volumes.hostPath.path'|[KSV006](https://avd.aquasec.com/appshield/ksv006)|\n\nMounting docker.sock from the host can give the container full root access to the host." + "text": "**Type**: kubernetes\n**Auditor Docs**: To find out more about the issue and how to fix it, follow [this link](https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/apparmor.md)\n**Description:** Finds containers that do not have AppArmor enabled\n\n *Note*: These audit results are generated with `kubeaudit`, a command line tool and a Go package that checks for potential security concerns in kubernetes manifest specs. You can read more about it at https://github.com/Shopify/kubeaudit " }, "properties": { "precision": "very-high", - "security-severity": "8.0", "tags": [ - "misconfiguration", "security", - "HIGH" + "kubernetes", + "infrastructure" ] } }, { - "id": "KSV017", - "name": "Misconfiguration", + "id": "AutomountServiceAccountTokenTrueAndDefaultSA", + "name": "asat", "shortDescription": { - "text": "KSV017" + "text": "AutomountServiceAccountTokenTrueAndDefaultSA" }, - "fullDescription": { - "text": "Privileged containers share namespaces with the host system and do not offer any security. They should be used exclusively for system containers that require high privileges." - }, - "defaultConfiguration": { - "level": "error" - }, - "helpUri": "https://avd.aquasec.com/appshield/ksv017", "help": { - "text": "Misconfiguration KSV017\nType: Kubernetes Security Check\nSeverity: HIGH\nCheck: Privileged container\nMessage: Container 'container' of DaemonSet 'daemonset' should set 'securityContext.privileged' to false\nLink: [KSV017](https://avd.aquasec.com/appshield/ksv017)\nPrivileged containers share namespaces with the host system and do not offer any security. They should be used exclusively for system containers that require high privileges.", - "markdown": "**Misconfiguration KSV017**\n| Type | Severity | Check | Message | Link |\n| --- | --- | --- | --- | --- |\n|Kubernetes Security Check|HIGH|Privileged container|Container 'container' of DaemonSet 'daemonset' should set 'securityContext.privileged' to false|[KSV017](https://avd.aquasec.com/appshield/ksv017)|\n\nPrivileged containers share namespaces with the host system and do not offer any security. They should be used exclusively for system containers that require high privileges." + "text": "**Type**: kubernetes\n**Auditor Docs**: To find out more about the issue and how to fix it, follow [this link](https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/asat.md)\n**Description:** Finds containers where the deprecated SA field is used or with a mounted default SA\n\n *Note*: These audit results are generated with `kubeaudit`, a command line tool and a Go package that checks for potential security concerns in kubernetes manifest specs. You can read more about it at https://github.com/Shopify/kubeaudit " }, "properties": { "precision": "very-high", - "security-severity": "8.0", "tags": [ - "misconfiguration", "security", - "HIGH" + "kubernetes", + "infrastructure" ] } } - ], - "version": "0.27.1" + ] } }, "results": [ { - "ruleId": "KSV008", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "Artifact: auditors/hostns/fixtures/host-ipc-true-allowed.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/hostns/fixtures/host-ipc-true-allowed.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV008", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "Artifact: auditors/hostns/fixtures/host-ipc-true.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/hostns/fixtures/host-ipc-true.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV009", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Artifact: auditors/hostns/fixtures/host-network-true-allowed.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/hostns/fixtures/host-network-true-allowed.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV009", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Artifact: auditors/hostns/fixtures/host-network-true.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/hostns/fixtures/host-network-true.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV010", - "ruleIndex": 2, - "level": "error", - "message": { - "text": "Artifact: auditors/hostns/fixtures/host-pid-true-allowed.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/hostns/fixtures/host-pid-true-allowed.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV010", - "ruleIndex": 2, - "level": "error", - "message": { - "text": "Artifact: auditors/hostns/fixtures/host-pid-true.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/hostns/fixtures/host-pid-true.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV008", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "Artifact: auditors/hostns/fixtures/namespaces-all-true-allowed.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/hostns/fixtures/namespaces-all-true-allowed.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV009", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Artifact: auditors/hostns/fixtures/namespaces-all-true-allowed.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/hostns/fixtures/namespaces-all-true-allowed.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV010", - "ruleIndex": 2, - "level": "error", - "message": { - "text": "Artifact: auditors/hostns/fixtures/namespaces-all-true-allowed.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/hostns/fixtures/namespaces-all-true-allowed.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV008", + "ruleId": "AppArmorInvalidAnnotation", "ruleIndex": 0, "level": "error", "message": { - "text": "Artifact: auditors/hostns/fixtures/namespaces-all-true.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" + "text": "Details: AppArmor annotation key refers to a container that doesn't exist. Remove the annotation 'container.apparmor.security.beta.kubernetes.io/container: badval'.\n Auditor: apparmor\nDescription: Finds containers that do not have AppArmor enabled\nAuditor docs: https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/apparmor.md " }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "auditors/hostns/fixtures/namespaces-all-true.yml", + "uri": "internal/sarif/fixtures/apparmor-invalid.yaml", "uriBaseId": "ROOTPATH" }, "region": { @@ -360,1004 +70,17 @@ ] }, { - "ruleId": "KSV009", + "ruleId": "AutomountServiceAccountTokenTrueAndDefaultSA", "ruleIndex": 1, "level": "error", "message": { - "text": "Artifact: auditors/hostns/fixtures/namespaces-all-true.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/hostns/fixtures/namespaces-all-true.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV010", - "ruleIndex": 2, - "level": "error", - "message": { - "text": "Artifact: auditors/hostns/fixtures/namespaces-all-true.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/hostns/fixtures/namespaces-all-true.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV006", - "ruleIndex": 3, - "level": "error", - "message": { - "text": "Artifact: auditors/mounts/fixtures/docker-sock-mounted.yml\nType: kubernetes\nVulnerability KSV006\nSeverity: HIGH\nMessage: Pod 'pod' should not specify '/var/run/docker.socker' in 'spec.template.volumes.hostPath.path'\nLink: [KSV006](https://avd.aquasec.com/appshield/ksv006)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/mounts/fixtures/docker-sock-mounted.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV017", - "ruleIndex": 4, - "level": "error", - "message": { - "text": "Artifact: auditors/privileged/fixtures/privileged-true-allowed-multi-containers-multi-labels.yml\nType: kubernetes\nVulnerability KSV017\nSeverity: HIGH\nMessage: Container 'container1' of DaemonSet 'daemonset' should set 'securityContext.privileged' to false\nLink: [KSV017](https://avd.aquasec.com/appshield/ksv017)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/privileged/fixtures/privileged-true-allowed-multi-containers-multi-labels.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV017", - "ruleIndex": 4, - "level": "error", - "message": { - "text": "Artifact: auditors/privileged/fixtures/privileged-true-allowed-multi-containers-multi-labels.yml\nType: kubernetes\nVulnerability KSV017\nSeverity: HIGH\nMessage: Container 'container2' of DaemonSet 'daemonset' should set 'securityContext.privileged' to false\nLink: [KSV017](https://avd.aquasec.com/appshield/ksv017)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/privileged/fixtures/privileged-true-allowed-multi-containers-multi-labels.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV017", - "ruleIndex": 4, - "level": "error", - "message": { - "text": "Artifact: auditors/privileged/fixtures/privileged-true-allowed-multi-containers-single-label.yml\nType: kubernetes\nVulnerability KSV017\nSeverity: HIGH\nMessage: Container 'container1' of DaemonSet 'daemonset' should set 'securityContext.privileged' to false\nLink: [KSV017](https://avd.aquasec.com/appshield/ksv017)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/privileged/fixtures/privileged-true-allowed-multi-containers-single-label.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV017", - "ruleIndex": 4, - "level": "error", - "message": { - "text": "Artifact: auditors/privileged/fixtures/privileged-true-allowed-multi-containers-single-label.yml\nType: kubernetes\nVulnerability KSV017\nSeverity: HIGH\nMessage: Container 'container2' of DaemonSet 'daemonset' should set 'securityContext.privileged' to false\nLink: [KSV017](https://avd.aquasec.com/appshield/ksv017)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/privileged/fixtures/privileged-true-allowed-multi-containers-single-label.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV017", - "ruleIndex": 4, - "level": "error", - "message": { - "text": "Artifact: auditors/privileged/fixtures/privileged-true-allowed.yml\nType: kubernetes\nVulnerability KSV017\nSeverity: HIGH\nMessage: Container 'container' of DaemonSet 'daemonset' should set 'securityContext.privileged' to false\nLink: [KSV017](https://avd.aquasec.com/appshield/ksv017)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/privileged/fixtures/privileged-true-allowed.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV017", - "ruleIndex": 4, - "level": "error", - "message": { - "text": "Artifact: auditors/privileged/fixtures/privileged-true.yml\nType: kubernetes\nVulnerability KSV017\nSeverity: HIGH\nMessage: Container 'container' of DaemonSet 'daemonset' should set 'securityContext.privileged' to false\nLink: [KSV017](https://avd.aquasec.com/appshield/ksv017)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "auditors/privileged/fixtures/privileged-true.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV008", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/cronjob.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: CronJob 'cronjob' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/cronjob.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV009", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/cronjob.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: CronJob 'cronjob' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/cronjob.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV010", - "ruleIndex": 2, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/cronjob.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: CronJob 'cronjob' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/cronjob.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV008", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/daemonset-v1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV009", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/daemonset-v1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV010", - "ruleIndex": 2, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/daemonset-v1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV008", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1beta1.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/daemonset-v1beta1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV009", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1beta1.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/daemonset-v1beta1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV010", - "ruleIndex": 2, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1beta1.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/daemonset-v1beta1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV008", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1beta2.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/daemonset-v1beta2.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV009", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1beta2.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/daemonset-v1beta2.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV010", - "ruleIndex": 2, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/daemonset-v1beta2.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: DaemonSet 'daemonset1' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/daemonset-v1beta2.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV008", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/deployment-apps-v1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV009", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/deployment-apps-v1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV010", - "ruleIndex": 2, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/deployment-apps-v1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV008", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1beta1.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/deployment-apps-v1beta1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV009", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1beta1.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/deployment-apps-v1beta1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV010", - "ruleIndex": 2, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1beta1.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/deployment-apps-v1beta1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV008", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1beta2.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/deployment-apps-v1beta2.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV009", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1beta2.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/deployment-apps-v1beta2.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV010", - "ruleIndex": 2, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/deployment-apps-v1beta2.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/deployment-apps-v1beta2.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV008", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/deployment-extensions-v1beta1.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/deployment-extensions-v1beta1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV009", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/deployment-extensions-v1beta1.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/deployment-extensions-v1beta1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV010", - "ruleIndex": 2, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/deployment-extensions-v1beta1.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Deployment 'deployment' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/deployment-extensions-v1beta1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV008", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/job.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Job 'job' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/job.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV009", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/job.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Job 'job' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/job.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV010", - "ruleIndex": 2, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/job.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Job 'job' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/job.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV008", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/pod.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/pod.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV009", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/pod.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/pod.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV010", - "ruleIndex": 2, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/pod.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: Pod 'pod' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/pod.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV008", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/replicationcontroller.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: ReplicationController 'replicationcontroller' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/replicationcontroller.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV009", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/replicationcontroller.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: ReplicationController 'replicationcontroller' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/replicationcontroller.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV010", - "ruleIndex": 2, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/replicationcontroller.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: ReplicationController 'replicationcontroller' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/replicationcontroller.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV008", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/statefulset-v1.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/statefulset-v1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV009", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/statefulset-v1.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/statefulset-v1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV010", - "ruleIndex": 2, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/statefulset-v1.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/statefulset-v1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV008", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/statefulset-v1beta1.yml\nType: kubernetes\nVulnerability KSV008\nSeverity: HIGH\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostIPC' to true\nLink: [KSV008](https://avd.aquasec.com/appshield/ksv008)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/statefulset-v1beta1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV009", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/statefulset-v1beta1.yml\nType: kubernetes\nVulnerability KSV009\nSeverity: HIGH\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostNetwork' to true\nLink: [KSV009](https://avd.aquasec.com/appshield/ksv009)" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/statefulset-v1beta1.yml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "KSV010", - "ruleIndex": 2, - "level": "error", - "message": { - "text": "Artifact: internal/test/fixtures/all_resources/statefulset-v1beta1.yml\nType: kubernetes\nVulnerability KSV010\nSeverity: HIGH\nMessage: StatefulSet 'statefulset' should not set 'spec.template.spec.hostPID' to true\nLink: [KSV010](https://avd.aquasec.com/appshield/ksv010)" + "text": "Details: Default service account with token mounted. automountServiceAccountToken should be set to 'false' on either the ServiceAccount or on the PodSpec or a non-default service account should be used.\n Auditor: asat\nDescription: Finds containers where the deprecated SA field is used or with a mounted default SA\nAuditor docs: https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/asat.md " }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "internal/test/fixtures/all_resources/statefulset-v1beta1.yml", + "uri": "internal/sarif/fixtures/apparmor-invalid.yaml", "uriBaseId": "ROOTPATH" }, "region": { diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go index d4fcb9c3..6a75d1a7 100644 --- a/internal/sarif/sarif.go +++ b/internal/sarif/sarif.go @@ -42,13 +42,14 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { docsURL = "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/" + auditor + ".md" } - helpMessage := fmt.Sprintf("**Type**: kubernetes\n**Docs**: %s\n**Description:** %s", docsURL, allAuditors[auditor]) + helpMessage := fmt.Sprintf("**Type**: kubernetes\n**Auditor Docs**: To find out more about the issue and how to fix it, follow [this link](%s)\n**Description:** %s\n\n *Note*: These audit results are generated with `kubeaudit`, a command line tool and a Go package that checks for potential security concerns in kubernetes manifest specs. You can read more about it at https://github.com/Shopify/kubeaudit ", + docsURL, allAuditors[auditor]) // we only add rules to the report based on the result findings run.AddRule(result.Rule). WithName(result.Auditor). WithMarkdownHelp(helpMessage). - WithHelp(&sarif.MultiformatMessageString{Text: &docsURL}). + WithHelp(&sarif.MultiformatMessageString{Text: &helpMessage}). WithShortDescription(&sarif.MultiformatMessageString{Text: &result.Rule}). WithProperties(sarif.Properties{ "tags": []string{ @@ -66,11 +67,14 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { severityLevel = "note" } + details := fmt.Sprintf("Details: %s\n Auditor: %s\nDescription: %s\nAuditor docs: %s ", + result.Message, result.Auditor, allAuditors[auditor], docsURL) + location := sarif.NewPhysicalLocation(). WithArtifactLocation(sarif.NewSimpleArtifactLocation(result.FilePath).WithUriBaseId("ROOTPATH")). WithRegion(sarif.NewRegion().WithStartLine(1)) result := sarif.NewRuleResult(result.Rule). - WithMessage(sarif.NewTextMessage(result.Message)). + WithMessage(sarif.NewTextMessage(details)). WithLevel(severityLevel). WithLocations([]*sarif.Location{sarif.NewLocation().WithPhysicalLocation(location)}) run.AddResult(result) diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index 7d91e872..77a46654 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -89,7 +89,7 @@ func TestCreate(t *testing.T) { ruleNames = append(ruleNames, sarifRule.ID) - assert.Equal(t, tc.expectedURI, *sarifRule.Help.Text) + assert.Contains(t, *sarifRule.Help.Text, tc.expectedURI) } for _, sarifResult := range sarifReport.Runs[0].Results { From 555562e04b778c396b6713dd56da50f8164f7c3a Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Fri, 22 Jul 2022 14:59:17 -0400 Subject: [PATCH 16/31] adds test assertion, description, fixes --- internal/sarif/fixtures/limits-nil.yaml | 8 ++++++++ internal/sarif/rules_test.go | 7 ++++++- internal/sarif/sarif.go | 10 +++++----- internal/sarif/sarif_test.go | 15 +++++++++++---- 4 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 internal/sarif/fixtures/limits-nil.yaml diff --git a/internal/sarif/fixtures/limits-nil.yaml b/internal/sarif/fixtures/limits-nil.yaml new file mode 100644 index 00000000..e26bd505 --- /dev/null +++ b/internal/sarif/fixtures/limits-nil.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod +spec: + containers: + - name: container + image: scratch diff --git a/internal/sarif/rules_test.go b/internal/sarif/rules_test.go index 4eba5371..780e230a 100644 --- a/internal/sarif/rules_test.go +++ b/internal/sarif/rules_test.go @@ -7,13 +7,18 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAuditorsLength(t *testing.T) { +func TestAuditorsLengthAndDescription(t *testing.T) { // if new auditors are created // make sure they're added with a matching description assert.Len(t, allAuditors, len(all.AuditorNames)) + for _, description := range allAuditors { + assert.True(t, description != "") + } } func TestViolationToRules(t *testing.T) { + // if new rules are added to any of the auditors + // they should be captured in the mapping cases := []struct { auditorName string expectedCount int diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go index 6a75d1a7..8e00a987 100644 --- a/internal/sarif/sarif.go +++ b/internal/sarif/sarif.go @@ -33,11 +33,13 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { for _, result := range results { severityLevel := result.Severity.String() + auditor := strings.ToLower(result.Auditor) var docsURL string auditor, ok := violationsToRules[result.Rule] + if ok { docsURL = "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/" + auditor + ".md" } @@ -48,7 +50,6 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { // we only add rules to the report based on the result findings run.AddRule(result.Rule). WithName(result.Auditor). - WithMarkdownHelp(helpMessage). WithHelp(&sarif.MultiformatMessageString{Text: &helpMessage}). WithShortDescription(&sarif.MultiformatMessageString{Text: &result.Rule}). WithProperties(sarif.Properties{ @@ -57,7 +58,6 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { "kubernetes", "infrastructure", }, - "precision": "very-high", }) // SARIF specifies the following severity levels: warning, error, note and none @@ -113,12 +113,12 @@ func validate(report io.Reader) (error, []gojsonschema.ResultError) { result, err := gojsonschema.Validate(schemaLoader, reportLoader) if err != nil { - panic(err.Error()) + return err, nil } if result.Valid() { return nil, nil - } else { - return nil, result.Errors() } + + return nil, result.Errors() } diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index 77a46654..b9b3cade 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -11,6 +11,7 @@ import ( "github.com/Shopify/kubeaudit/auditors/apparmor" "github.com/Shopify/kubeaudit/auditors/capabilities" "github.com/Shopify/kubeaudit/auditors/image" + "github.com/Shopify/kubeaudit/auditors/limits" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -19,10 +20,10 @@ func TestCreate(t *testing.T) { capabilitiesAuditable := capabilities.New(capabilities.Config{}) apparmorAuditable := apparmor.New() imageAuditable := image.New(image.Config{Image: "scratch:1.5"}) + limitsAuditable, _ := limits.New(limits.Config{}) cases := []struct { file string - auditorName string auditors []kubeaudit.Auditable expectedRule string expectedErrorLevel string @@ -31,7 +32,6 @@ func TestCreate(t *testing.T) { }{ { "apparmor-invalid.yaml", - apparmor.Name, []kubeaudit.Auditable{apparmorAuditable}, apparmor.AppArmorInvalidAnnotation, "error", @@ -40,7 +40,6 @@ func TestCreate(t *testing.T) { }, { "capabilities-added.yaml", - capabilities.Name, []kubeaudit.Auditable{capabilitiesAuditable}, capabilities.CapabilityAdded, "error", @@ -49,13 +48,20 @@ func TestCreate(t *testing.T) { }, { "image-tag-present.yaml", - capabilities.Name, []kubeaudit.Auditable{imageAuditable}, image.ImageCorrect, "note", "Image tag is correct", "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/image.md", }, + { + "limits-nil.yaml", + []kubeaudit.Auditable{limitsAuditable}, + limits.LimitsNotSet, + "warning", + "Resource limits not set.", + "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/limits.md", + }, } for _, tc := range cases { @@ -79,6 +85,7 @@ func TestCreate(t *testing.T) { assert.Equal(t, sarifReport.Runs[0].Tool.Driver.Rules[0].ID, tc.expectedRule) var ruleNames []string + // check for rules occurrences for _, sarifRule := range sarifReport.Runs[0].Tool.Driver.Rules { assert.Equal(t, sarifRule.Properties["tags"], []string{ From 1687df995c6dca9cbdea3c38a69d1fa4c9c63cf0 Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Fri, 22 Jul 2022 16:38:08 -0400 Subject: [PATCH 17/31] check that rules are only added when violations found --- example_custom_test.go | 1 + internal/sarif/fixtures/apparmor-valid.yaml | 11 +++++++++++ internal/sarif/sarif_test.go | 14 ++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 internal/sarif/fixtures/apparmor-valid.yaml diff --git a/example_custom_test.go b/example_custom_test.go index 1c8a5a5c..9f15d4f7 100644 --- a/example_custom_test.go +++ b/example_custom_test.go @@ -29,6 +29,7 @@ type myAuditor struct{} func (a *myAuditor) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudit.AuditResult, error) { return []*kubeaudit.AuditResult{ { + Auditor: "Awesome", Rule: "MyAudit", Severity: kubeaudit.Error, Message: "My custom error", diff --git a/internal/sarif/fixtures/apparmor-valid.yaml b/internal/sarif/fixtures/apparmor-valid.yaml new file mode 100644 index 00000000..7833c2e3 --- /dev/null +++ b/internal/sarif/fixtures/apparmor-valid.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod + namespace: apparmor-enabled + annotations: + container.apparmor.security.beta.kubernetes.io/container: localhost/something +spec: + containers: + - name: container + image: scratch diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index b9b3cade..ca144faf 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -62,6 +62,14 @@ func TestCreate(t *testing.T) { "Resource limits not set.", "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/limits.md", }, + { + "apparmor-valid.yaml", + []kubeaudit.Auditable{apparmorAuditable}, + "", + "", + "", + "", + }, } for _, tc := range cases { @@ -81,6 +89,12 @@ func TestCreate(t *testing.T) { assert.Equal(t, "https://github.com/Shopify/kubeaudit", *sarifReport.Runs[0].Tool.Driver.InformationURI) + // verify that we only add rules to the report + // if vulnerabilities are found + if len(kubeAuditReport.Results()) == 0 { + break + } + // verify that the rules have been added as per report findings assert.Equal(t, sarifReport.Runs[0].Tool.Driver.Rules[0].ID, tc.expectedRule) From 6f633ec46223be1a8ccf4fd20bed2ee12221ccb8 Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Mon, 25 Jul 2022 11:16:48 -0400 Subject: [PATCH 18/31] separates test for config file with no kubeuadit errors --- internal/sarif/sarif_test.go | 38 ++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index ca144faf..07962b2b 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -16,7 +16,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestCreate(t *testing.T) { +func TestCreateWithResults(t *testing.T) { capabilitiesAuditable := capabilities.New(capabilities.Config{}) apparmorAuditable := apparmor.New() imageAuditable := image.New(image.Config{Image: "scratch:1.5"}) @@ -62,14 +62,6 @@ func TestCreate(t *testing.T) { "Resource limits not set.", "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/limits.md", }, - { - "apparmor-valid.yaml", - []kubeaudit.Auditable{apparmorAuditable}, - "", - "", - "", - "", - }, } for _, tc := range cases { @@ -89,12 +81,6 @@ func TestCreate(t *testing.T) { assert.Equal(t, "https://github.com/Shopify/kubeaudit", *sarifReport.Runs[0].Tool.Driver.InformationURI) - // verify that we only add rules to the report - // if vulnerabilities are found - if len(kubeAuditReport.Results()) == 0 { - break - } - // verify that the rules have been added as per report findings assert.Equal(t, sarifReport.Runs[0].Tool.Driver.Rules[0].ID, tc.expectedRule) @@ -156,3 +142,25 @@ func TestValidate(t *testing.T) { } } } + +func TestCreateWithNoResults(t *testing.T) { + apparmorAuditable := apparmor.New() + + fixture := filepath.Join("fixtures", "apparmor-valid.yaml") + auditor, err := kubeaudit.New([]kubeaudit.Auditable{apparmorAuditable}) + require.NoError(t, err) + + manifest, openErr := os.Open(fixture) + require.NoError(t, openErr) + + kubeAuditReport, err := auditor.AuditManifest(fixture, manifest) + require.NoError(t, err) + + sarifReport, err := Create(kubeAuditReport) + require.NoError(t, err) + + require.NotEmpty(t, *sarifReport.Runs[0]) + + // verify that the rules are only added as per report findings + assert.Len(t, sarifReport.Runs[0].Tool.Driver.Rules, 0) +} From edae749393afb7f26ce513fe27340424d2780605 Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Mon, 25 Jul 2022 14:37:01 -0400 Subject: [PATCH 19/31] rebases against main - explicit markdown --- go.mod | 6 ++---- go.sum | 6 ------ internal/sarif/sarif.go | 6 ++++-- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 4de78030..8fae50a1 100644 --- a/go.mod +++ b/go.mod @@ -2,15 +2,16 @@ module github.com/Shopify/kubeaudit require ( github.com/jetstack/cert-manager v1.6.1 + github.com/owenrumney/go-sarif/v2 v2.1.2 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.5.0 github.com/stretchr/testify v1.8.0 + github.com/xeipuuv/gojsonschema v1.2.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.24.3 k8s.io/apiextensions-apiserver v0.23.5 k8s.io/apimachinery v0.24.3 k8s.io/client-go v0.24.3 - github.com/owenrumney/go-sarif/v2 v2.1.2 ) require ( @@ -46,13 +47,10 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/qri-io/jsonpointer v0.1.1 // indirect - github.com/qri-io/jsonschema v0.2.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.4.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect diff --git a/go.sum b/go.sum index c8d78ea9..5b63da88 100644 --- a/go.sum +++ b/go.sum @@ -954,10 +954,6 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/qri-io/jsonpointer v0.1.1 h1:prVZBZLL6TW5vsSB9fFHFAMBLI4b0ri5vribQlTJiBA= -github.com/qri-io/jsonpointer v0.1.1/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64= -github.com/qri-io/jsonschema v0.2.1 h1:NNFoKms+kut6ABPf6xiKNM5214jzxAhDBrPHCJ97Wg0= -github.com/qri-io/jsonschema v0.2.1/go.mod h1:g7DPkiOsK1xv6T/Ao5scXRkd+yTFygcANPBaaqW+VrI= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -987,7 +983,6 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -1422,7 +1417,6 @@ golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go index 8e00a987..9a889c5b 100644 --- a/internal/sarif/sarif.go +++ b/internal/sarif/sarif.go @@ -44,13 +44,15 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { docsURL = "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/" + auditor + ".md" } - helpMessage := fmt.Sprintf("**Type**: kubernetes\n**Auditor Docs**: To find out more about the issue and how to fix it, follow [this link](%s)\n**Description:** %s\n\n *Note*: These audit results are generated with `kubeaudit`, a command line tool and a Go package that checks for potential security concerns in kubernetes manifest specs. You can read more about it at https://github.com/Shopify/kubeaudit ", + helpText := fmt.Sprintf("Type: kubernetes\nAuditor Docs: To find out more about the issue and how to fix it, follow [this link](%s)\nDescription: %s\n\n Note: These audit results are generated with `kubeaudit`, a command line tool and a Go package that checks for potential security concerns in kubernetes manifest specs. You can read more about it at https://github.com/Shopify/kubeaudit ", docsURL, allAuditors[auditor]) + + helpMarkdown := fmt.Sprintf("**Type**: kubernetes\n**Auditor Docs**: To find out more about the issue and how to fix it, follow [this link](%s)\n**Description:** %s\n\n *Note*: These audit results are generated with `kubeaudit`, a command line tool and a Go package that checks for potential security concerns in kubernetes manifest specs. You can read more about it at https://github.com/Shopify/kubeaudit ", docsURL, allAuditors[auditor]) // we only add rules to the report based on the result findings run.AddRule(result.Rule). WithName(result.Auditor). - WithHelp(&sarif.MultiformatMessageString{Text: &helpMessage}). + WithHelp(&sarif.MultiformatMessageString{Text: &helpText, Markdown: &helpMarkdown}). WithShortDescription(&sarif.MultiformatMessageString{Text: &result.Rule}). WithProperties(sarif.Properties{ "tags": []string{ From 2ba56a8cd9b09b6b62e8e9227211d44f84dabeea Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Mon, 25 Jul 2022 17:04:09 -0400 Subject: [PATCH 20/31] fixt: expected vs actual test order --- internal/sarif/sarif_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index 07962b2b..a186291f 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -82,17 +82,19 @@ func TestCreateWithResults(t *testing.T) { *sarifReport.Runs[0].Tool.Driver.InformationURI) // verify that the rules have been added as per report findings - assert.Equal(t, sarifReport.Runs[0].Tool.Driver.Rules[0].ID, tc.expectedRule) + assert.Equal(t, tc.expectedRule, sarifReport.Runs[0].Tool.Driver.Rules[0].ID) var ruleNames []string // check for rules occurrences for _, sarifRule := range sarifReport.Runs[0].Tool.Driver.Rules { - assert.Equal(t, sarifRule.Properties["tags"], []string{ + assert.Equal(t, []string{ "security", "kubernetes", "infrastructure", - }) + }, + sarifRule.Properties["tags"], + ) ruleNames = append(ruleNames, sarifRule.ID) From c486c0afefebb4b622d927a2351f50913f742641 Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Mon, 1 Aug 2022 12:19:00 -0400 Subject: [PATCH 21/31] feedback - part1 --- cmd/commands/root.go | 2 +- internal/sarif/rules.go | 2 +- internal/sarif/rules_test.go | 2 +- internal/sarif/sarif.go | 4 ++-- internal/sarif/sarif_test.go | 4 ++++ internal/test/test.go | 1 + 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/cmd/commands/root.go b/cmd/commands/root.go index c759bc8a..502099f9 100644 --- a/cmd/commands/root.go +++ b/cmd/commands/root.go @@ -84,7 +84,7 @@ func runAudit(auditable ...kubeaudit.Auditable) func(cmd *cobra.Command, args [] sarifReport, err := sarif.Create(report) if err != nil { - log.WithError(err).Fatal("error generating the SARIF output") + log.WithError(err).Fatal("Error generating the SARIF output") } sarifReport.WriteFile(rootConfig.sarifOut) diff --git a/internal/sarif/rules.go b/internal/sarif/rules.go index e09dc121..8fa622dc 100644 --- a/internal/sarif/rules.go +++ b/internal/sarif/rules.go @@ -21,7 +21,7 @@ var allAuditors = map[string]string{ apparmor.Name: "Finds containers that do not have AppArmor enabled", asat.Name: "Finds containers where the deprecated SA field is used or with a mounted default SA", capabilities.Name: "Finds containers that do not drop the recommended capabilities or add new ones", - deprecatedapis.Name: "Finds any resource defined with a deprecated API version.", + deprecatedapis.Name: "Finds any resource defined with a deprecated API version", hostns.Name: "Finds containers that have HostPID, HostIPC or HostNetwork enabled", image.Name: "Finds containers which do not use the desired version of an image (via the tag) or use an image without a tag", limits.Name: "Finds containers which exceed the specified CPU and memory limits or do not specify any", diff --git a/internal/sarif/rules_test.go b/internal/sarif/rules_test.go index 780e230a..39d91bff 100644 --- a/internal/sarif/rules_test.go +++ b/internal/sarif/rules_test.go @@ -12,7 +12,7 @@ func TestAuditorsLengthAndDescription(t *testing.T) { // make sure they're added with a matching description assert.Len(t, allAuditors, len(all.AuditorNames)) for _, description := range allAuditors { - assert.True(t, description != "") + assert.NotEmpty(t, description) } } diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go index 9a889c5b..e133efba 100644 --- a/internal/sarif/sarif.go +++ b/internal/sarif/sarif.go @@ -64,7 +64,7 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { // SARIF specifies the following severity levels: warning, error, note and none // https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html - // so we're converting info to none here so we get valid SARIF output + // so we're converting info to note here so we get valid SARIF output if result.Severity.String() == "info" { severityLevel = "note" } @@ -91,7 +91,7 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { err, errs := validate(&reportBytes) if err != nil { - return nil, fmt.Errorf("error validating SARIF schema: %s", err) + return nil, fmt.Errorf("error validating SARIF schema: %w", err) } if len(errs) > 0 { diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index a186291f..9164df1f 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -72,6 +72,8 @@ func TestCreateWithResults(t *testing.T) { manifest, openErr := os.Open(fixture) require.NoError(t, openErr) + defer manifest.Close() + kubeAuditReport, err := auditor.AuditManifest(fixture, manifest) require.NoError(t, err) @@ -155,6 +157,8 @@ func TestCreateWithNoResults(t *testing.T) { manifest, openErr := os.Open(fixture) require.NoError(t, openErr) + defer manifest.Close() + kubeAuditReport, err := auditor.AuditManifest(fixture, manifest) require.NoError(t, err) diff --git a/internal/test/test.go b/internal/test/test.go index bf22435d..b7f4e599 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -107,6 +107,7 @@ func GetReport(t *testing.T, fixtureDir, fixture string, auditables []kubeaudit. case MANIFEST_MODE: manifest, openErr := os.Open(fixture) require.NoError(openErr) + defer manifest.Close() report, err = auditor.AuditManifest("", manifest) case LOCAL_MODE: defer DeleteNamespace(t, namespace) From 36ffd3bd29155688ba9a646a623201fe87e46212 Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Mon, 1 Aug 2022 14:38:27 -0400 Subject: [PATCH 22/31] feedback: adds subtests --- internal/sarif/sarif_test.go | 94 +++++++++++++++++++----------------- profile.out | 25 ++++++++++ 2 files changed, 74 insertions(+), 45 deletions(-) create mode 100644 profile.out diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index 9164df1f..a50e3851 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -59,56 +59,58 @@ func TestCreateWithResults(t *testing.T) { []kubeaudit.Auditable{limitsAuditable}, limits.LimitsNotSet, "warning", - "Resource limits not set.", + "Resource limits not set", "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/limits.md", }, } for _, tc := range cases { - fixture := filepath.Join("fixtures", tc.file) - auditor, err := kubeaudit.New(tc.auditors) - require.NoError(t, err) + t.Run(tc.file, func(t *testing.T) { + fixture := filepath.Join("fixtures", tc.file) + auditor, err := kubeaudit.New(tc.auditors) + require.NoError(t, err) - manifest, openErr := os.Open(fixture) - require.NoError(t, openErr) + manifest, openErr := os.Open(fixture) + require.NoError(t, openErr) - defer manifest.Close() + defer manifest.Close() - kubeAuditReport, err := auditor.AuditManifest(fixture, manifest) - require.NoError(t, err) + kubeAuditReport, err := auditor.AuditManifest(fixture, manifest) + require.NoError(t, err) - sarifReport, err := Create(kubeAuditReport) - require.NoError(t, err) + sarifReport, err := Create(kubeAuditReport) + require.NoError(t, err) - assert.Equal(t, "https://github.com/Shopify/kubeaudit", - *sarifReport.Runs[0].Tool.Driver.InformationURI) + assert.Equal(t, "https://github.com/Shopify/kubeaudit", + *sarifReport.Runs[0].Tool.Driver.InformationURI) - // verify that the rules have been added as per report findings - assert.Equal(t, tc.expectedRule, sarifReport.Runs[0].Tool.Driver.Rules[0].ID) + // verify that the rules have been added as per report findings + assert.Equal(t, tc.expectedRule, sarifReport.Runs[0].Tool.Driver.Rules[0].ID) - var ruleNames []string + var ruleNames []string - // check for rules occurrences - for _, sarifRule := range sarifReport.Runs[0].Tool.Driver.Rules { - assert.Equal(t, []string{ - "security", - "kubernetes", - "infrastructure", - }, - sarifRule.Properties["tags"], - ) + // check for rules occurrences + for _, sarifRule := range sarifReport.Runs[0].Tool.Driver.Rules { + assert.Equal(t, []string{ + "security", + "kubernetes", + "infrastructure", + }, + sarifRule.Properties["tags"], + ) - ruleNames = append(ruleNames, sarifRule.ID) + ruleNames = append(ruleNames, sarifRule.ID) - assert.Contains(t, *sarifRule.Help.Text, tc.expectedURI) - } + assert.Contains(t, *sarifRule.Help.Text, tc.expectedURI) + } - for _, sarifResult := range sarifReport.Runs[0].Results { - assert.Contains(t, ruleNames, *sarifResult.RuleID) - assert.Equal(t, tc.expectedErrorLevel, *sarifResult.Level) - assert.Contains(t, *sarifResult.Message.Text, tc.expectedMessage) - assert.Contains(t, "sarif/fixtures/"+tc.file, *sarifResult.Locations[0].PhysicalLocation.ArtifactLocation.URI) - } + for _, sarifResult := range sarifReport.Runs[0].Results { + assert.Contains(t, ruleNames, *sarifResult.RuleID) + assert.Equal(t, tc.expectedErrorLevel, *sarifResult.Level) + assert.Contains(t, *sarifResult.Message.Text, tc.expectedMessage) + assert.Contains(t, "sarif/fixtures/"+tc.file, *sarifResult.Locations[0].PhysicalLocation.ArtifactLocation.URI) + } + }) } } @@ -129,21 +131,23 @@ func TestValidate(t *testing.T) { } for _, tc := range cases { - var reportBytes bytes.Buffer + t.Run(tc.file, func(t *testing.T) { + var reportBytes bytes.Buffer - testSarif, err := ioutil.ReadFile("fixtures/" + tc.file) - require.NoError(t, err) + testSarif, err := ioutil.ReadFile("fixtures/" + tc.file) + require.NoError(t, err) - reportBytes.Write(testSarif) + reportBytes.Write(testSarif) - err, errs := validate(&reportBytes) - require.NoError(t, err) + err, errs := validate(&reportBytes) + require.NoError(t, err) - if !tc.shouldBeValid { - assert.True(t, len(errs) > 0) - } else { - assert.Len(t, errs, 0) - } + if !tc.shouldBeValid { + assert.True(t, len(errs) > 0) + } else { + assert.Len(t, errs, 0) + } + }) } } diff --git a/profile.out b/profile.out new file mode 100644 index 00000000..67503b3a --- /dev/null +++ b/profile.out @@ -0,0 +1,25 @@ +mode: atomic +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:15.71,18.16 2 5 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:23.2,29.57 4 5 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:34.2,34.33 1 5 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:85.2,88.16 3 5 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:92.2,93.16 2 5 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:97.2,97.19 1 5 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:101.2,101.20 1 5 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:18.16,20.3 1 0 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:29.57,32.3 2 4 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:34.33,43.9 5 5 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:47.3,68.41 4 5 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:72.3,82.24 4 5 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:43.9,45.4 1 5 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:68.41,70.4 1 1 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:88.16,90.3 1 0 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:93.16,95.3 1 0 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:97.19,99.3 1 0 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:106.69,111.8 4 7 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:115.2,117.16 2 7 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:121.2,121.20 1 7 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:125.2,125.29 1 1 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:111.8,113.3 1 7 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:117.16,119.3 1 0 +github.com/Shopify/kubeaudit/internal/sarif/sarif.go:121.20,123.3 1 6 From 2f32539e30db10993d9b5c06b8fd0f0aa1d7c88a Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Wed, 10 Aug 2022 10:13:13 -0400 Subject: [PATCH 23/31] genevieve's feedback - pt 1 --- .gitignore | 1 + .../deprecatedapis/depreceatedapis_test.go | 11 +++----- internal/sarif/rules_test.go | 6 ++--- internal/sarif/sarif.go | 6 +++-- internal/sarif/sarif_test.go | 2 +- profile.out | 25 ------------------- result.go | 4 +-- 7 files changed, 15 insertions(+), 40 deletions(-) delete mode 100644 profile.out diff --git a/.gitignore b/.gitignore index d886a4db..26bb0065 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ coverage.txt /vendor /.vscode .go-version +profile.out diff --git a/auditors/deprecatedapis/depreceatedapis_test.go b/auditors/deprecatedapis/depreceatedapis_test.go index eb892e27..e6bd2cc0 100644 --- a/auditors/deprecatedapis/depreceatedapis_test.go +++ b/auditors/deprecatedapis/depreceatedapis_test.go @@ -2,7 +2,6 @@ package deprecatedapis import ( "fmt" - "os" "strings" "testing" @@ -55,13 +54,11 @@ func TestAuditDeprecatedAPIs(t *testing.T) { report := test.AuditManifest(t, fixtureDir, tc.file, auditor, []string{DeprecatedAPIUsed}) assertReport(t, report, tc.expectedSeverity, message, metadata) - // disable local tests when running in dev mode - if os.Getenv("USE_KIND") == "false" { - return - } - report = test.AuditLocal(t, fixtureDir, tc.file, auditor, fmt.Sprintf("%s-%d", strings.Split(tc.file, ".")[0], i), []string{DeprecatedAPIUsed}) - assertReport(t, report, tc.expectedSeverity, message, metadata) + + if report != nil { + assertReport(t, report, tc.expectedSeverity, message, metadata) + } }) } } diff --git a/internal/sarif/rules_test.go b/internal/sarif/rules_test.go index 39d91bff..0ec65cdf 100644 --- a/internal/sarif/rules_test.go +++ b/internal/sarif/rules_test.go @@ -10,9 +10,9 @@ import ( func TestAuditorsLengthAndDescription(t *testing.T) { // if new auditors are created // make sure they're added with a matching description - assert.Len(t, allAuditors, len(all.AuditorNames)) - for _, description := range allAuditors { - assert.NotEmpty(t, description) + for _, auditorName := range all.AuditorNames { + description, ok := allAuditors[auditorName] + assert.Truef(t, ok && description != "", "missing description for auditor %s", auditorName) } } diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go index e133efba..6d87e47e 100644 --- a/internal/sarif/sarif.go +++ b/internal/sarif/sarif.go @@ -11,6 +11,8 @@ import ( "github.com/xeipuuv/gojsonschema" ) +const repoURL = "https://github.com/Shopify/kubeaudit" + // Create generates new sarif Report or returns an error func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { // create a new report object @@ -20,7 +22,7 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { } // create a run for kubeaudit - run := sarif.NewRunWithInformationURI("kubeaudit", "https://github.com/Shopify/kubeaudit") + run := sarif.NewRunWithInformationURI("kubeaudit", repoURL) report.AddRun(run) @@ -65,7 +67,7 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { // SARIF specifies the following severity levels: warning, error, note and none // https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html // so we're converting info to note here so we get valid SARIF output - if result.Severity.String() == "info" { + if result.Severity.String() == kubeaudit.Info.String() { severityLevel = "note" } diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index a50e3851..436f5993 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -81,7 +81,7 @@ func TestCreateWithResults(t *testing.T) { sarifReport, err := Create(kubeAuditReport) require.NoError(t, err) - assert.Equal(t, "https://github.com/Shopify/kubeaudit", + assert.Equal(t, repoURL, *sarifReport.Runs[0].Tool.Driver.InformationURI) // verify that the rules have been added as per report findings diff --git a/profile.out b/profile.out deleted file mode 100644 index 67503b3a..00000000 --- a/profile.out +++ /dev/null @@ -1,25 +0,0 @@ -mode: atomic -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:15.71,18.16 2 5 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:23.2,29.57 4 5 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:34.2,34.33 1 5 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:85.2,88.16 3 5 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:92.2,93.16 2 5 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:97.2,97.19 1 5 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:101.2,101.20 1 5 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:18.16,20.3 1 0 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:29.57,32.3 2 4 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:34.33,43.9 5 5 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:47.3,68.41 4 5 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:72.3,82.24 4 5 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:43.9,45.4 1 5 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:68.41,70.4 1 1 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:88.16,90.3 1 0 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:93.16,95.3 1 0 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:97.19,99.3 1 0 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:106.69,111.8 4 7 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:115.2,117.16 2 7 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:121.2,121.20 1 7 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:125.2,125.29 1 1 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:111.8,113.3 1 7 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:117.16,119.3 1 0 -github.com/Shopify/kubeaudit/internal/sarif/sarif.go:121.20,123.3 1 6 diff --git a/result.go b/result.go index dd941bb8..d23e0675 100644 --- a/result.go +++ b/result.go @@ -37,13 +37,13 @@ func (s SeverityLevel) String() string { // AuditResult represents a potential security issue. There may be multiple AuditResults per resource and audit type AuditResult struct { - Auditor string // auditor name + Auditor string // Auditor name Rule string // Rule uniquely identifies a type of violation Severity SeverityLevel // Severity is one of Error, Warn, or Info Message string // Message is a human-readable description of the audit result PendingFix PendingFix // PendingFix is the fix that will be applied to automatically fix the security issue Metadata Metadata // Metadata includes additional context for an audit result - FilePath string // manifest file path + FilePath string // Manifest file path } func (result *AuditResult) Fix(resource k8s.Resource) (newResources []k8s.Resource) { From a386749ae5253e6cef9d998a495f8768a6474e40 Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Tue, 16 Aug 2022 15:18:00 -0400 Subject: [PATCH 24/31] feedback pt 2:removes sarif flag --- README.md | 7 +++++-- cmd/commands/root.go | 15 +++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9c688cfa..68de5e1f 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,10 @@ The minimum severity level can be set using the `--minSeverity/-m` flag. By default kubeaudit will output results in a human-readable way. If the output is intended to be further processed, it can be set to output JSON using the `--format json` flag. To output results as logs (the previous default) use `--format logrus`. Some output formats include colors to make results easier to read in a terminal. To disable colors (for example, if you are sending output to a text file), you can use the `--no-color` flag. -You can generate a kubeaudit report in [SARIF](https://docs.oasis-open.org/sarif/sarif/v2.0/sarif-v2.0.html) and write it to a file by using the `-s/--sarif` flag. +You can generate a kubeaudit report in [SARIF](https://docs.oasis-open.org/sarif/sarif/v2.0/sarif-v2.0.html) and write it to a file by using the `--format sarif` flag and redirect the stdout output to a file with `>`. For example: +``` +kubeaudit all -f path-to-my-file.yaml --format="sarif" > example.sarif +``` If there are results of severity level `error`, kubeaudit will exit with exit code 2. This can be changed using the `--exitcode/-e` flag. @@ -214,7 +217,7 @@ Auditors can also be run individually. | Short | Long | Description | | :---- | :----------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | -| | --format | The output format to use (one of "pretty", "logrus", "json") (default is "pretty") | +| | --format | The output format to use (one of "sarif", "pretty", "logrus", "json") (default is "pretty") | | | --kubeconfig | Path to local Kubernetes config file. Only used in local mode (default is `$HOME/.kube/config`) | | -c | --context | The name of the kubeconfig context to use | | -f | --manifest | Path to the yaml configuration to audit. Only used in manifest mode. You may use `-` to read from stdin. | diff --git a/cmd/commands/root.go b/cmd/commands/root.go index 502099f9..b1ad8e8d 100644 --- a/cmd/commands/root.go +++ b/cmd/commands/root.go @@ -19,7 +19,6 @@ var rootConfig rootFlags type rootFlags struct { format string - sarifOut string kubeConfig string context string manifest string @@ -54,8 +53,7 @@ func init() { RootCmd.PersistentFlags().StringVarP(&rootConfig.kubeConfig, "kubeconfig", "", "", "Path to local Kubernetes config file. Only used in local mode (default is $HOME/.kube/config)") RootCmd.PersistentFlags().StringVarP(&rootConfig.context, "context", "c", "", "The name of the kubeconfig context to use") RootCmd.PersistentFlags().StringVarP(&rootConfig.minSeverity, "minseverity", "m", "info", "Set the lowest severity level to report (one of \"error\", \"warning\", \"info\")") - RootCmd.PersistentFlags().StringVarP(&rootConfig.format, "format", "p", "pretty", "The output format to use (one of \"pretty\", \"logrus\", \"json\")") - RootCmd.PersistentFlags().StringVarP(&rootConfig.sarifOut, "sarif", "s", "", "The path to output sarif report to") + RootCmd.PersistentFlags().StringVarP(&rootConfig.format, "format", "p", "pretty", "The output format to use (one of \"sarif\",\"pretty\", \"logrus\", \"json\")") RootCmd.PersistentFlags().StringVarP(&rootConfig.namespace, "namespace", "n", apiv1.NamespaceAll, "Only audit resources in the specified namespace. Not currently supported in manifest mode.") RootCmd.PersistentFlags().BoolVarP(&rootConfig.includeGenerated, "includegenerated", "g", false, "Include generated resources in scan (eg. pods generated by deployments).") RootCmd.PersistentFlags().BoolVar(&rootConfig.noColor, "no-color", false, "Don't produce colored output.") @@ -80,17 +78,14 @@ func runAudit(auditable ...kubeaudit.Auditable) func(cmd *cobra.Command, args [] kubeaudit.WithColor(!rootConfig.noColor), } - if rootConfig.sarifOut != "" { + switch rootConfig.format { + case "sarif": sarifReport, err := sarif.Create(report) - if err != nil { log.WithError(err).Fatal("Error generating the SARIF output") } - - sarifReport.WriteFile(rootConfig.sarifOut) - } - - switch rootConfig.format { + sarifReport.PrettyWrite(os.Stdout) + return case "json": printOptions = append(printOptions, kubeaudit.WithFormatter(&log.JSONFormatter{})) case "logrus": From d6072b1b1952c7f9e57480c2de45f50a4a2f1dbc Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Tue, 16 Aug 2022 15:55:19 -0400 Subject: [PATCH 25/31] pete's feedback: add missing auditor name to redundant override --- auditors/asat/asat.go | 2 +- auditors/capabilities/capabilities.go | 2 +- auditors/hostns/hostns.go | 2 +- auditors/mounts/mounts.go | 2 +- auditors/netpols/netpols.go | 8 ++++---- auditors/nonroot/nonroot.go | 2 +- auditors/privesc/privesc.go | 2 +- auditors/privileged/privileged.go | 2 +- auditors/rootfs/rootfs.go | 2 +- pkg/override/override.go | 7 ++++--- 10 files changed, 16 insertions(+), 15 deletions(-) diff --git a/auditors/asat/asat.go b/auditors/asat/asat.go index b20dc7da..c9ef1e3f 100644 --- a/auditors/asat/asat.go +++ b/auditors/asat/asat.go @@ -29,7 +29,7 @@ func New() *AutomountServiceAccountToken { // being automatically mounted func (a *AutomountServiceAccountToken) Audit(resource k8s.Resource, resources []k8s.Resource) ([]*kubeaudit.AuditResult, error) { auditResult := auditResource(resource, resources) - auditResult = override.ApplyOverride(auditResult, "", resource, OverrideLabel) + auditResult = override.ApplyOverride(auditResult, Name, "", resource, OverrideLabel) if auditResult != nil { return []*kubeaudit.AuditResult{auditResult}, nil } diff --git a/auditors/capabilities/capabilities.go b/auditors/capabilities/capabilities.go index a59079da..f433c432 100644 --- a/auditors/capabilities/capabilities.go +++ b/auditors/capabilities/capabilities.go @@ -49,7 +49,7 @@ func (a *Capabilities) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeau for _, capability := range uniqueCapabilities(container) { for _, auditResult := range auditContainer(container, capability, a.allowAddList) { - auditResult = override.ApplyOverride(auditResult, container.Name, resource, getOverrideLabel(capability)) + auditResult = override.ApplyOverride(auditResult, Name, container.Name, resource, getOverrideLabel(capability)) if auditResult != nil { auditResults = append(auditResults, auditResult) } diff --git a/auditors/hostns/hostns.go b/auditors/hostns/hostns.go index 318f4da9..232a43e4 100644 --- a/auditors/hostns/hostns.go +++ b/auditors/hostns/hostns.go @@ -46,7 +46,7 @@ func (a *HostNamespaces) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kube {auditHostPID, HostPIDOverrideLabel}, } { auditResult := check.auditFunc(podSpec) - auditResult = override.ApplyOverride(auditResult, "", resource, check.overrideLabel) + auditResult = override.ApplyOverride(auditResult, Name, "", resource, check.overrideLabel) if auditResult != nil { auditResults = append(auditResults, auditResult) } diff --git a/auditors/mounts/mounts.go b/auditors/mounts/mounts.go index 104db4ef..fdd66193 100644 --- a/auditors/mounts/mounts.go +++ b/auditors/mounts/mounts.go @@ -61,7 +61,7 @@ func (sensitive *SensitivePathMounts) Audit(resource k8s.Resource, _ []k8s.Resou for _, container := range k8s.GetContainers(resource) { for _, auditResult := range auditContainer(container, sensitiveVolumes) { - auditResult = override.ApplyOverride(auditResult, container.Name, resource, getOverrideLabel(auditResult.Metadata[MountNameMetadataKey])) + auditResult = override.ApplyOverride(auditResult, Name, container.Name, resource, getOverrideLabel(auditResult.Metadata[MountNameMetadataKey])) if auditResult != nil { auditResults = append(auditResults, auditResult) } diff --git a/auditors/netpols/netpols.go b/auditors/netpols/netpols.go index 233eb873..6938eccd 100644 --- a/auditors/netpols/netpols.go +++ b/auditors/netpols/netpols.go @@ -122,7 +122,7 @@ func auditNetworkPoliciesForDenyAll(resource k8s.Resource, resources []k8s.Resou policyType: Ingress, }, } - auditResult = override.ApplyOverride(auditResult, "", resource, IngressOverrideLabel) + auditResult = override.ApplyOverride(auditResult, Name, "", resource, IngressOverrideLabel) auditResults = append(auditResults, auditResult) } @@ -140,7 +140,7 @@ func auditNetworkPoliciesForDenyAll(resource k8s.Resource, resources []k8s.Resou policyType: Egress, }, } - auditResult = override.ApplyOverride(auditResult, "", resource, EgressOverrideLabel) + auditResult = override.ApplyOverride(auditResult, Name, "", resource, EgressOverrideLabel) auditResults = append(auditResults, auditResult) } @@ -197,7 +197,7 @@ func auditNetworkPoliciesForDenyAll(resource k8s.Resource, resources []k8s.Resou namespace: namespace, }, } - auditResult = override.ApplyOverride(auditResult, "", resource, IngressOverrideLabel) + auditResult = override.ApplyOverride(auditResult, Name, "", resource, IngressOverrideLabel) auditResults = append(auditResults, auditResult) auditResult = &kubeaudit.AuditResult{ @@ -213,7 +213,7 @@ func auditNetworkPoliciesForDenyAll(resource k8s.Resource, resources []k8s.Resou namespace: namespace, }, } - auditResult = override.ApplyOverride(auditResult, "", resource, EgressOverrideLabel) + auditResult = override.ApplyOverride(auditResult, Name, "", resource, EgressOverrideLabel) auditResults = append(auditResults, auditResult) return auditResults diff --git a/auditors/nonroot/nonroot.go b/auditors/nonroot/nonroot.go index 7dba3e0a..5452c1cc 100644 --- a/auditors/nonroot/nonroot.go +++ b/auditors/nonroot/nonroot.go @@ -38,7 +38,7 @@ func (a *RunAsNonRoot) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeau for _, container := range k8s.GetContainers(resource) { auditResult := auditContainer(container, resource) - auditResult = override.ApplyOverride(auditResult, container.Name, resource, OverrideLabel) + auditResult = override.ApplyOverride(auditResult, Name, container.Name, resource, OverrideLabel) if auditResult != nil { auditResults = append(auditResults, auditResult) } diff --git a/auditors/privesc/privesc.go b/auditors/privesc/privesc.go index 2833a56a..95a86929 100644 --- a/auditors/privesc/privesc.go +++ b/auditors/privesc/privesc.go @@ -32,7 +32,7 @@ func (a *AllowPrivilegeEscalation) Audit(resource k8s.Resource, _ []k8s.Resource for _, container := range k8s.GetContainers(resource) { auditResult := auditContainer(container) - auditResult = override.ApplyOverride(auditResult, container.Name, resource, OverrideLabel) + auditResult = override.ApplyOverride(auditResult, Name, container.Name, resource, OverrideLabel) if auditResult != nil { auditResults = append(auditResults, auditResult) } diff --git a/auditors/privileged/privileged.go b/auditors/privileged/privileged.go index 296d9dfe..1d276834 100644 --- a/auditors/privileged/privileged.go +++ b/auditors/privileged/privileged.go @@ -31,7 +31,7 @@ func (a *Privileged) Audit(resource k8s.Resource, _ []k8s.Resource) ([]*kubeaudi for _, container := range k8s.GetContainers(resource) { auditResult := auditContainer(container, resource) - auditResult = override.ApplyOverride(auditResult, container.Name, resource, OverrideLabel) + auditResult = override.ApplyOverride(auditResult, Name, container.Name, resource, OverrideLabel) if auditResult != nil { auditResults = append(auditResults, auditResult) } diff --git a/auditors/rootfs/rootfs.go b/auditors/rootfs/rootfs.go index 5f71eb5b..a9e44e8f 100644 --- a/auditors/rootfs/rootfs.go +++ b/auditors/rootfs/rootfs.go @@ -31,7 +31,7 @@ func (a *ReadOnlyRootFilesystem) Audit(resource k8s.Resource, _ []k8s.Resource) for _, container := range k8s.GetContainers(resource) { auditResult := auditContainer(container, resource) - auditResult = override.ApplyOverride(auditResult, container.Name, resource, OverrideLabel) + auditResult = override.ApplyOverride(auditResult, Name, container.Name, resource, OverrideLabel) if auditResult != nil { auditResults = append(auditResults, auditResult) } diff --git a/pkg/override/override.go b/pkg/override/override.go index c1318d22..018ed951 100644 --- a/pkg/override/override.go +++ b/pkg/override/override.go @@ -24,8 +24,9 @@ func GetOverriddenResultName(resultName string) string { // NewRedundantOverrideResult creates a new AuditResult at warning level telling the user to remove the override // label because there are no security issues found, so the label is redundant -func NewRedundantOverrideResult(containerName string, overrideReason, overrideLabel string) *kubeaudit.AuditResult { +func NewRedundantOverrideResult(auditorName, containerName, overrideReason, overrideLabel string) *kubeaudit.AuditResult { return &kubeaudit.AuditResult{ + Auditor: auditorName, Rule: kubeaudit.RedundantAuditorOverride, Severity: kubeaudit.Warn, Message: "Auditor is disabled via label but there were no security issues found by the auditor. The label should be removed.", @@ -38,7 +39,7 @@ func NewRedundantOverrideResult(containerName string, overrideReason, overrideLa // ApplyOverride checks if hasOverride is true. If it is, it changes the severity of the audit result from error to // warn, adds the override reason to the metadata and removes the pending fix -func ApplyOverride(auditResult *kubeaudit.AuditResult, containerName string, resource k8s.Resource, overrideLabel string) *kubeaudit.AuditResult { +func ApplyOverride(auditResult *kubeaudit.AuditResult, auditorName, containerName string, resource k8s.Resource, overrideLabel string) *kubeaudit.AuditResult { hasOverride, overrideReason := GetContainerOverrideReason(containerName, resource, overrideLabel) if !hasOverride { @@ -46,7 +47,7 @@ func ApplyOverride(auditResult *kubeaudit.AuditResult, containerName string, res } if auditResult == nil { - return NewRedundantOverrideResult(containerName, overrideReason, overrideLabel) + return NewRedundantOverrideResult(auditorName, containerName, overrideReason, overrideLabel) } auditResult.Rule = GetOverriddenResultName(auditResult.Rule) From c492afa84ba336d931910a3ac3d093df64fd3a76 Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Tue, 16 Aug 2022 16:23:12 -0400 Subject: [PATCH 26/31] removes uneccessary mapping and reassigning --- internal/sarif/sarif.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go index 6d87e47e..3e05ac56 100644 --- a/internal/sarif/sarif.go +++ b/internal/sarif/sarif.go @@ -38,13 +38,7 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { auditor := strings.ToLower(result.Auditor) - var docsURL string - - auditor, ok := violationsToRules[result.Rule] - - if ok { - docsURL = "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/" + auditor + ".md" - } + docsURL := "https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/" + auditor + ".md" helpText := fmt.Sprintf("Type: kubernetes\nAuditor Docs: To find out more about the issue and how to fix it, follow [this link](%s)\nDescription: %s\n\n Note: These audit results are generated with `kubeaudit`, a command line tool and a Go package that checks for potential security concerns in kubernetes manifest specs. You can read more about it at https://github.com/Shopify/kubeaudit ", docsURL, allAuditors[auditor]) From f06028aedf605e67ec8df91a362b47b3a39d0a4b Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Tue, 16 Aug 2022 17:26:56 -0400 Subject: [PATCH 27/31] more feedback: validate func arguments - implementation vs interface) --- internal/sarif/sarif.go | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go index 3e05ac56..29584432 100644 --- a/internal/sarif/sarif.go +++ b/internal/sarif/sarif.go @@ -3,7 +3,6 @@ package sarif import ( "bytes" "fmt" - "io" "strings" "github.com/Shopify/kubeaudit" @@ -99,17 +98,10 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { // Validates that the SARIF file is valid as per sarif spec // https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Documents/CommitteeSpecifications/2.1.0/sarif-schema-2.1.0.json -func validate(report io.Reader) (error, []gojsonschema.ResultError) { +func validate(report *bytes.Buffer) (error, []gojsonschema.ResultError) { schemaLoader := gojsonschema.NewReferenceLoader("http://json.schemastore.org/sarif-2.1.0") - var reportLoader gojsonschema.JSONLoader - - _, ok := report.(*bytes.Buffer) - if ok { - reportLoader = gojsonschema.NewStringLoader(report.(*bytes.Buffer).String()) - } - + reportLoader := gojsonschema.NewStringLoader(report.String()) result, err := gojsonschema.Validate(schemaLoader, reportLoader) - if err != nil { return err, nil } From 6b094a185471ceefed92f2c50755db1b84b77b3e Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Tue, 16 Aug 2022 17:47:12 -0400 Subject: [PATCH 28/31] feedback: removes validation -air gapped envs --- internal/sarif/fixtures/invalid.sarif | 92 ------------------------- internal/sarif/fixtures/valid.sarif | 96 --------------------------- internal/sarif/sarif.go | 27 -------- internal/sarif/sarif_test.go | 39 ----------- 4 files changed, 254 deletions(-) delete mode 100644 internal/sarif/fixtures/invalid.sarif delete mode 100644 internal/sarif/fixtures/valid.sarif diff --git a/internal/sarif/fixtures/invalid.sarif b/internal/sarif/fixtures/invalid.sarif deleted file mode 100644 index 95bdbe44..00000000 --- a/internal/sarif/fixtures/invalid.sarif +++ /dev/null @@ -1,92 +0,0 @@ -{ - "version": "2.1.0", - "$schema": "https://json.schemastore.org/sarif-2.1.0-rtm.5.json", - "runs": [ - { - "tool": { - "driver": { - "informationUri": "https://github.com/Shopify/kubeaudit", - "name": "kubeaudit", - "rules": [ - { - "id": "AppArmorInvalidAnnotation", - "name": "apparmor", - "shortDescription": null, - "help": { - "markdown": "**Type**: kubernetes\n**Docs**: https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/apparmor.md\n**Description:** Finds containers that do not have AppArmor enabled" - }, - "properties": { - "precision": "very-high", - "tags": [ - "security", - "kubernetes", - "infrastructure" - ] - } - }, - { - "id": "AutomountServiceAccountTokenTrueAndDefaultSA", - "name": "asat", - "shortDescription": null, - "help": { - "markdown": "**Type**: kubernetes\n**Docs**: \n**Description:** Finds containers where the deprecated SA field is used or with a mounted default SA" - }, - "properties": { - "precision": "very-high", - "tags": [ - "security", - "kubernetes", - "infrastructure" - ] - } - } - ] - } - }, - "results": [ - { - "ruleId": "AppArmorInvalidAnnotation", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "AppArmor annotation key refers to a container that doesn't exist. Remove the annotation 'container.apparmor.security.beta.kubernetes.io/container: badval'." - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/sarif/fixtures/apparmor-disabled.yaml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "AutomountServiceAccountTokenTrueAndDefaultSA", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Default service account with token mounted. automountServiceAccountToken should be set to 'false' on either the ServiceAccount or on the PodSpec or a non-default service account should be used." - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/sarif/fixtures/apparmor-disabled.yaml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/internal/sarif/fixtures/valid.sarif b/internal/sarif/fixtures/valid.sarif deleted file mode 100644 index 2328dd73..00000000 --- a/internal/sarif/fixtures/valid.sarif +++ /dev/null @@ -1,96 +0,0 @@ -{ - "version": "2.1.0", - "$schema": "https://json.schemastore.org/sarif-2.1.0-rtm.5.json", - "runs": [ - { - "tool": { - "driver": { - "informationUri": "https://github.com/Shopify/kubeaudit", - "name": "kubeaudit", - "rules": [ - { - "id": "AppArmorInvalidAnnotation", - "name": "apparmor", - "shortDescription": { - "text": "AppArmorInvalidAnnotation" - }, - "help": { - "text": "**Type**: kubernetes\n**Auditor Docs**: To find out more about the issue and how to fix it, follow [this link](https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/apparmor.md)\n**Description:** Finds containers that do not have AppArmor enabled\n\n *Note*: These audit results are generated with `kubeaudit`, a command line tool and a Go package that checks for potential security concerns in kubernetes manifest specs. You can read more about it at https://github.com/Shopify/kubeaudit " - }, - "properties": { - "precision": "very-high", - "tags": [ - "security", - "kubernetes", - "infrastructure" - ] - } - }, - { - "id": "AutomountServiceAccountTokenTrueAndDefaultSA", - "name": "asat", - "shortDescription": { - "text": "AutomountServiceAccountTokenTrueAndDefaultSA" - }, - "help": { - "text": "**Type**: kubernetes\n**Auditor Docs**: To find out more about the issue and how to fix it, follow [this link](https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/asat.md)\n**Description:** Finds containers where the deprecated SA field is used or with a mounted default SA\n\n *Note*: These audit results are generated with `kubeaudit`, a command line tool and a Go package that checks for potential security concerns in kubernetes manifest specs. You can read more about it at https://github.com/Shopify/kubeaudit " - }, - "properties": { - "precision": "very-high", - "tags": [ - "security", - "kubernetes", - "infrastructure" - ] - } - } - ] - } - }, - "results": [ - { - "ruleId": "AppArmorInvalidAnnotation", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "Details: AppArmor annotation key refers to a container that doesn't exist. Remove the annotation 'container.apparmor.security.beta.kubernetes.io/container: badval'.\n Auditor: apparmor\nDescription: Finds containers that do not have AppArmor enabled\nAuditor docs: https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/apparmor.md " - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/sarif/fixtures/apparmor-invalid.yaml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - }, - { - "ruleId": "AutomountServiceAccountTokenTrueAndDefaultSA", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "Details: Default service account with token mounted. automountServiceAccountToken should be set to 'false' on either the ServiceAccount or on the PodSpec or a non-default service account should be used.\n Auditor: asat\nDescription: Finds containers where the deprecated SA field is used or with a mounted default SA\nAuditor docs: https://github.com/Shopify/kubeaudit/blob/main/docs/auditors/asat.md " - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "internal/sarif/fixtures/apparmor-invalid.yaml", - "uriBaseId": "ROOTPATH" - }, - "region": { - "startLine": 1 - } - } - } - ] - } - ] - } - ] -} diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go index 29584432..07c77174 100644 --- a/internal/sarif/sarif.go +++ b/internal/sarif/sarif.go @@ -7,7 +7,6 @@ import ( "github.com/Shopify/kubeaudit" "github.com/owenrumney/go-sarif/v2/sarif" - "github.com/xeipuuv/gojsonschema" ) const repoURL = "https://github.com/Shopify/kubeaudit" @@ -84,31 +83,5 @@ func Create(kubeauditReport *kubeaudit.Report) (*sarif.Report, error) { return nil, nil } - err, errs := validate(&reportBytes) - if err != nil { - return nil, fmt.Errorf("error validating SARIF schema: %w", err) - } - - if len(errs) > 0 { - return nil, fmt.Errorf("SARIF schema validation errors: %s", errs) - } - return report, nil } - -// Validates that the SARIF file is valid as per sarif spec -// https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Documents/CommitteeSpecifications/2.1.0/sarif-schema-2.1.0.json -func validate(report *bytes.Buffer) (error, []gojsonschema.ResultError) { - schemaLoader := gojsonschema.NewReferenceLoader("http://json.schemastore.org/sarif-2.1.0") - reportLoader := gojsonschema.NewStringLoader(report.String()) - result, err := gojsonschema.Validate(schemaLoader, reportLoader) - if err != nil { - return err, nil - } - - if result.Valid() { - return nil, nil - } - - return nil, result.Errors() -} diff --git a/internal/sarif/sarif_test.go b/internal/sarif/sarif_test.go index 436f5993..8dd45cb4 100644 --- a/internal/sarif/sarif_test.go +++ b/internal/sarif/sarif_test.go @@ -1,8 +1,6 @@ package sarif import ( - "bytes" - "io/ioutil" "os" "path/filepath" "testing" @@ -114,43 +112,6 @@ func TestCreateWithResults(t *testing.T) { } } -func TestValidate(t *testing.T) { - - cases := []struct { - file string - shouldBeValid bool - }{ - { - file: "invalid.sarif", - shouldBeValid: false, - }, - { - file: "valid.sarif", - shouldBeValid: true, - }, - } - - for _, tc := range cases { - t.Run(tc.file, func(t *testing.T) { - var reportBytes bytes.Buffer - - testSarif, err := ioutil.ReadFile("fixtures/" + tc.file) - require.NoError(t, err) - - reportBytes.Write(testSarif) - - err, errs := validate(&reportBytes) - require.NoError(t, err) - - if !tc.shouldBeValid { - assert.True(t, len(errs) > 0) - } else { - assert.Len(t, errs, 0) - } - }) - } -} - func TestCreateWithNoResults(t *testing.T) { apparmorAuditable := apparmor.New() From b1b6a0454fbc2558bd7f7190ccf3347aa2d217e6 Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Wed, 17 Aug 2022 15:25:35 -0400 Subject: [PATCH 29/31] feedback: removes stdin dash appended to filepath --- cmd/commands/root.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/commands/root.go b/cmd/commands/root.go index b1ad8e8d..b9f05d7c 100644 --- a/cmd/commands/root.go +++ b/cmd/commands/root.go @@ -107,6 +107,7 @@ func getReport(auditors ...kubeaudit.Auditable) *kubeaudit.Report { var f *os.File if rootConfig.manifest == "-" { f = os.Stdin + rootConfig.manifest = "" } else { manifest, err := os.Open(rootConfig.manifest) if err != nil { From bfd28db632067ecc2f17c6e4655288e51000f9b4 Mon Sep 17 00:00:00 2001 From: Dani Santos Date: Wed, 17 Aug 2022 17:16:34 -0400 Subject: [PATCH 30/31] Update README.md Co-authored-by: Genevieve Luyt <11131143+genevieveluyt@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 68de5e1f..1ffcc0ab 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ The minimum severity level can be set using the `--minSeverity/-m` flag. By default kubeaudit will output results in a human-readable way. If the output is intended to be further processed, it can be set to output JSON using the `--format json` flag. To output results as logs (the previous default) use `--format logrus`. Some output formats include colors to make results easier to read in a terminal. To disable colors (for example, if you are sending output to a text file), you can use the `--no-color` flag. -You can generate a kubeaudit report in [SARIF](https://docs.oasis-open.org/sarif/sarif/v2.0/sarif-v2.0.html) and write it to a file by using the `--format sarif` flag and redirect the stdout output to a file with `>`. For example: +You can generate a kubeaudit report in [SARIF](https://docs.oasis-open.org/sarif/sarif/v2.0/sarif-v2.0.html) using the `--format sarif` flag. To write the SARIF results to a file, you can redirect the output with `>`. For example: ``` kubeaudit all -f path-to-my-file.yaml --format="sarif" > example.sarif ``` From f539847eb18d239d6a2a41e71c73b38774eefa6c Mon Sep 17 00:00:00 2001 From: Daniele Santos Date: Wed, 17 Aug 2022 17:32:31 -0400 Subject: [PATCH 31/31] removes ref to flag and violations to rules mapping --- README.md | 1 - internal/sarif/rules.go | 45 -------------------- internal/sarif/rules_test.go | 80 ------------------------------------ 3 files changed, 126 deletions(-) diff --git a/README.md b/README.md index 1ffcc0ab..83c27466 100644 --- a/README.md +++ b/README.md @@ -226,7 +226,6 @@ Auditors can also be run individually. | -m | --minseverity | Set the lowest severity level to report (one of "error", "warning", "info") (default is "info") | | -e | --exitcode | Exit code to use if there are results with severity of "error". Conventionally, 0 is used for success and all non-zero codes for an error. (default is 2) | | | --no-color | Don't use colors in the output (default is false) | -| -s | --sarif | The file location to save the SARIF output | ## Configuration File diff --git a/internal/sarif/rules.go b/internal/sarif/rules.go index 8fa622dc..9944acda 100644 --- a/internal/sarif/rules.go +++ b/internal/sarif/rules.go @@ -33,48 +33,3 @@ var allAuditors = map[string]string{ rootfs.Name: "Finds containers which do not have a read-only filesystem", seccomp.Name: "Finds containers running without seccomp", } - -var violationsToRules = map[string]string{ - apparmor.AppArmorAnnotationMissing: apparmor.Name, - apparmor.AppArmorDisabled: apparmor.Name, - apparmor.AppArmorInvalidAnnotation: apparmor.Name, - asat.AutomountServiceAccountTokenDeprecated: asat.Name, - asat.AutomountServiceAccountTokenTrueAndDefaultSA: asat.Name, - capabilities.CapabilityAdded: capabilities.Name, - capabilities.CapabilityOrSecurityContextMissing: capabilities.Name, - capabilities.CapabilityShouldDropAll: capabilities.Name, - deprecatedapis.DeprecatedAPIUsed: deprecatedapis.Name, - hostns.NamespaceHostIPCTrue: hostns.Name, - hostns.NamespaceHostNetworkTrue: hostns.Name, - hostns.NamespaceHostPIDTrue: hostns.Name, - image.ImageCorrect: image.Name, - image.ImageTagIncorrect: image.Name, - image.ImageTagMissing: image.Name, - limits.LimitsCPUExceeded: limits.Name, - limits.LimitsCPUNotSet: limits.Name, - limits.LimitsMemoryExceeded: limits.Name, - limits.LimitsMemoryNotSet: limits.Name, - limits.LimitsNotSet: limits.Name, - mounts.SensitivePathsMounted: mounts.Name, - netpols.MissingDefaultDenyIngressAndEgressNetworkPolicy: netpols.Name, - netpols.MissingDefaultDenyIngressNetworkPolicy: netpols.Name, - netpols.MissingDefaultDenyEgressNetworkPolicy: netpols.Name, - netpols.AllowAllIngressNetworkPolicyExists: netpols.Name, - netpols.AllowAllEgressNetworkPolicyExists: netpols.Name, - nonroot.RunAsUserCSCRoot: nonroot.Name, - nonroot.RunAsUserPSCRoot: nonroot.Name, - nonroot.RunAsNonRootCSCFalse: nonroot.Name, - nonroot.RunAsNonRootPSCNilCSCNil: nonroot.Name, - nonroot.RunAsNonRootPSCFalseCSCNil: nonroot.Name, - privesc.AllowPrivilegeEscalationNil: privesc.Name, - privesc.AllowPrivilegeEscalationTrue: privesc.Name, - privileged.PrivilegedTrue: privileged.Name, - privileged.PrivilegedNil: privileged.Name, - rootfs.ReadOnlyRootFilesystemFalse: rootfs.Name, - rootfs.ReadOnlyRootFilesystemNil: rootfs.Name, - seccomp.SeccompAnnotationMissing: seccomp.Name, - seccomp.SeccompDeprecatedPod: seccomp.Name, - seccomp.SeccompDisabledPod: seccomp.Name, - seccomp.SeccompDeprecatedContainer: seccomp.Name, - seccomp.SeccompDisabledContainer: seccomp.Name, -} diff --git a/internal/sarif/rules_test.go b/internal/sarif/rules_test.go index 0ec65cdf..1737392a 100644 --- a/internal/sarif/rules_test.go +++ b/internal/sarif/rules_test.go @@ -15,83 +15,3 @@ func TestAuditorsLengthAndDescription(t *testing.T) { assert.Truef(t, ok && description != "", "missing description for auditor %s", auditorName) } } - -func TestViolationToRules(t *testing.T) { - // if new rules are added to any of the auditors - // they should be captured in the mapping - cases := []struct { - auditorName string - expectedCount int - }{ - { - "apparmor", - 3, - }, - { - "asat", - 2, - }, - { - "capabilities", - 3, - }, - { - "deprecatedapis", - 1, - }, - { - "hostns", - 3, - }, - { - "image", - 3, - }, - { - "limits", - 5, - }, - { - "mounts", - 1, - }, - { - "netpols", - 5, - }, - { - "nonroot", - 5, - }, - { - "privesc", - 2, - }, - { - "privileged", - 2, - }, - { - "rootfs", - 2, - }, - { - "seccomp", - 5, - }, - } - - assert.Len(t, cases, len(all.AuditorNames)) - - for _, c := range cases { - var totalCount int - - for _, v := range violationsToRules { - if v == c.auditorName { - totalCount += 1 - } - } - - assert.Equal(t, c.expectedCount, totalCount) - } -}