Skip to content
This repository has been archived by the owner on Oct 30, 2024. It is now read-only.

Commit

Permalink
adds support for sarif output (#453)
Browse files Browse the repository at this point in the history
* adds support for sarif
  • Loading branch information
dani-santos-code authored Aug 18, 2022
1 parent df0fd92 commit 32a65c8
Show file tree
Hide file tree
Showing 37 changed files with 518 additions and 82 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ coverage.txt
/vendor
/.vscode
.go-version
profile.out
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ 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) 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
```

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).
Expand Down Expand Up @@ -212,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. |
Expand Down
9 changes: 6 additions & 3 deletions auditors/apparmor/apparmor.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi

if isAppArmorAnnotationMissing(containerAnnotation, annotations) {
return &kubeaudit.AuditResult{
Name: AppArmorAnnotationMissing,
Auditor: Name,
Rule: AppArmorAnnotationMissing,
Severity: kubeaudit.Error,
Message: fmt.Sprintf("AppArmor annotation missing. The annotation '%s' should be added.", containerAnnotation),
Metadata: kubeaudit.Metadata{
Expand All @@ -80,7 +81,8 @@ func auditContainer(container *k8s.ContainerV1, resource k8s.Resource) *kubeaudi

if isAppArmorDisabled(containerAnnotation, annotations) {
return &kubeaudit.AuditResult{
Name: AppArmorDisabled,
Auditor: Name,
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{
Expand All @@ -107,7 +109,8 @@ func auditPodAnnotations(resource k8s.Resource, containerNames []string) []*kube
containerName := strings.Split(annotationKey, "/")[1]
if !contains(containerNames, containerName) {
auditResults = append(auditResults, &kubeaudit.AuditResult{
Name: AppArmorInvalidAnnotation,
Auditor: Name,
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{
Expand Down
8 changes: 5 additions & 3 deletions auditors/asat/asat.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -45,7 +45,8 @@ func auditResource(resource k8s.Resource, resources []k8s.Resource) *kubeaudit.A

if isDeprecatedServiceAccountName(podSpec) && !hasServiceAccountName(podSpec) {
return &kubeaudit.AuditResult{
Name: AutomountServiceAccountTokenDeprecated,
Auditor: Name,
Rule: AutomountServiceAccountTokenDeprecated,
Severity: kubeaudit.Warn,
Message: "serviceAccount is a deprecated alias for serviceAccountName. serviceAccountName should be used instead.",
PendingFix: &fixDeprecatedServiceAccountName{
Expand All @@ -60,7 +61,8 @@ func auditResource(resource k8s.Resource, resources []k8s.Resource) *kubeaudit.A
defaultServiceAccount := getDefaultServiceAccount(resources)
if usesDefaultServiceAccount(podSpec) && isAutomountTokenTrue(podSpec, defaultServiceAccount) {
return &kubeaudit.AuditResult{
Name: AutomountServiceAccountTokenTrueAndDefaultSA,
Auditor: Name,
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{
Expand Down
11 changes: 7 additions & 4 deletions auditors/capabilities/capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -75,7 +75,8 @@ 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{
Name: CapabilityAdded,
Auditor: Name,
Rule: CapabilityAdded,
Severity: kubeaudit.Error,
Message: message,
PendingFix: &fixCapabilityAdded{
Expand Down Expand Up @@ -103,7 +104,8 @@ 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{
Name: CapabilityOrSecurityContextMissing,
Auditor: Name,
Rule: CapabilityOrSecurityContextMissing,
Severity: kubeaudit.Error,
Message: message,
PendingFix: &fixMissingSecurityContextOrCapability{
Expand All @@ -118,7 +120,8 @@ 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{
Name: CapabilityShouldDropAll,
Auditor: Name,
Rule: CapabilityShouldDropAll,
Severity: kubeaudit.Error,
Message: message,
PendingFix: &fixCapabilityNotDroppedAll{
Expand Down
3 changes: 2 additions & 1 deletion auditors/deprecatedapis/depreceatedapis.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ func (deprecatedAPIs *DeprecatedAPIs) Audit(resource k8s.Resource, _ []k8s.Resou
}
}
auditResult := &kubeaudit.AuditResult{
Name: DeprecatedAPIUsed,
Auditor: Name,
Rule: DeprecatedAPIUsed,
Severity: severity,
Message: deprecationMessage,
Metadata: metadata,
Expand Down
6 changes: 5 additions & 1 deletion auditors/deprecatedapis/depreceatedapis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,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)

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)
}
})
}
}
Expand Down
11 changes: 7 additions & 4 deletions auditors/hostns/hostns.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -62,7 +62,8 @@ func auditHostNetwork(podSpec *k8s.PodSpecV1) *kubeaudit.AuditResult {
metadata["PodHost"] = podSpec.Hostname
}
return &kubeaudit.AuditResult{
Name: NamespaceHostNetworkTrue,
Auditor: Name,
Rule: NamespaceHostNetworkTrue,
Severity: kubeaudit.Error,
Message: "hostNetwork is set to 'true' in PodSpec. It should be set to 'false'.",
PendingFix: &fixHostNetworkTrue{
Expand All @@ -82,7 +83,8 @@ func auditHostIPC(podSpec *k8s.PodSpecV1) *kubeaudit.AuditResult {
metadata["PodHost"] = podSpec.Hostname
}
return &kubeaudit.AuditResult{
Name: NamespaceHostIPCTrue,
Auditor: Name,
Rule: NamespaceHostIPCTrue,
Severity: kubeaudit.Error,
Message: "hostIPC is set to 'true' in PodSpec. It should be set to 'false'.",
PendingFix: &fixHostIPCTrue{
Expand All @@ -102,7 +104,8 @@ func auditHostPID(podSpec *k8s.PodSpecV1) *kubeaudit.AuditResult {
metadata["PodHost"] = podSpec.Hostname
}
return &kubeaudit.AuditResult{
Name: NamespaceHostPIDTrue,
Auditor: Name,
Rule: NamespaceHostPIDTrue,
Severity: kubeaudit.Error,
Message: "hostPID is set to 'true' in PodSpec. It should be set to 'false'.",
PendingFix: &fixHostPIDTrue{
Expand Down
9 changes: 6 additions & 3 deletions auditors/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ func auditContainer(container *k8s.ContainerV1, image string) *kubeaudit.AuditRe

if isImageTagMissing(containerTag) {
return &kubeaudit.AuditResult{
Name: ImageTagMissing,
Auditor: Name,
Rule: ImageTagMissing,
Severity: kubeaudit.Warn,
Message: "Image tag is missing.",
Metadata: kubeaudit.Metadata{
Expand All @@ -61,7 +62,8 @@ func auditContainer(container *k8s.ContainerV1, image string) *kubeaudit.AuditRe

if isImageTagIncorrect(name, tag, containerName, containerTag) {
return &kubeaudit.AuditResult{
Name: ImageTagIncorrect,
Auditor: Name,
Rule: ImageTagIncorrect,
Severity: kubeaudit.Error,
Message: fmt.Sprintf("Container tag is incorrect. It should be set to '%s'.", tag),
Metadata: kubeaudit.Metadata{
Expand All @@ -72,7 +74,8 @@ func auditContainer(container *k8s.ContainerV1, image string) *kubeaudit.AuditRe

if isImageCorrect(name, tag, containerName, containerTag) {
return &kubeaudit.AuditResult{
Name: ImageCorrect,
Auditor: Name,
Rule: ImageCorrect,
Severity: kubeaudit.Info,
Message: "Image tag is correct",
Metadata: kubeaudit.Metadata{
Expand Down
15 changes: 10 additions & 5 deletions auditors/limits/limits.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ 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{
Name: LimitsNotSet,
Auditor: Name,
Rule: LimitsNotSet,
Severity: kubeaudit.Warn,
Message: "Resource limits not set.",
Metadata: kubeaudit.Metadata{
Expand All @@ -81,7 +82,8 @@ func (limits *Limits) auditContainer(container *k8s.ContainerV1) (auditResults [

if isCPULimitUnset(container) {
auditResult := &kubeaudit.AuditResult{
Name: LimitsCPUNotSet,
Auditor: Name,
Rule: LimitsCPUNotSet,
Severity: kubeaudit.Warn,
Message: "Resource CPU limit not set.",
Metadata: kubeaudit.Metadata{
Expand All @@ -92,7 +94,8 @@ func (limits *Limits) auditContainer(container *k8s.ContainerV1) (auditResults [
} else if exceedsCPULimit(container, limits) {
maxCPU := limits.maxCPU.String()
auditResult := &kubeaudit.AuditResult{
Name: LimitsCPUExceeded,
Auditor: Name,
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{
Expand All @@ -106,7 +109,8 @@ func (limits *Limits) auditContainer(container *k8s.ContainerV1) (auditResults [

if isMemoryLimitUnset(container) {
auditResult := &kubeaudit.AuditResult{
Name: LimitsMemoryNotSet,
Auditor: Name,
Rule: LimitsMemoryNotSet,
Severity: kubeaudit.Warn,
Message: "Resource Memory limit not set.",
Metadata: kubeaudit.Metadata{
Expand All @@ -117,7 +121,8 @@ func (limits *Limits) auditContainer(container *k8s.ContainerV1) (auditResults [
} else if exceedsMemoryLimit(container, limits) {
maxMemory := limits.maxMemory.String()
auditResult := &kubeaudit.AuditResult{
Name: LimitsMemoryExceeded,
Auditor: Name,
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{
Expand Down
5 changes: 3 additions & 2 deletions auditors/mounts/mounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -100,7 +100,8 @@ 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{
Name: SensitivePathsMounted,
Auditor: Name,
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{
Expand Down
Loading

0 comments on commit 32a65c8

Please sign in to comment.