-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #25 from cultureamp/add-ignore-file
feat: allow findings to be ignored
- Loading branch information
Showing
23 changed files
with
1,098 additions
and
157 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# Ignoring findings | ||
|
||
Findings can be ignored using a YAML file with the following structure: | ||
|
||
```yaml | ||
ignores: | ||
- id: CVE-2023-100 | ||
- id: CVE-2023-200 | ||
until: 2023-12-31 | ||
reason: allowing 2 weeks for base image to update | ||
- id: CVE-2023-300 | ||
``` | ||
- each element must have at least the `id` field | ||
- the `until` field defines the expiry of this ignore entry. This allows a team time to respond while temporarily allowing builds to continue. | ||
- the `reason` field gives a justification that is rendered in the annotation for greater visibility. Including the "why" in this field is highly recommended. | ||
|
||
Ignore configuration can be specified in a number of places. If a listing for a finding with the same CVE name appears in multiple files, the most local wins: central configuration can be overridden by the repository. | ||
|
||
From least to most important: | ||
|
||
- `/etc/ecr-scan-results-buildkite-plugin/ignore.y[a]ml` (part of the agent, not modifiable by builds) | ||
- `buildkite/ecr-scan-results-ignore.y[a]ml` (specified alongside the pipeline) | ||
- `.buildkite/ecr-scan-results-ignore.y[a]ml` | ||
- `.ecr-scan-results-ignore.y[a]ml` (local repository configuration) | ||
|
||
Configuration in the `/etc/ecr-scan-results-buildkite-plugin` directory allows for organizations to ship agents with plugin configuration that centrally manages findings that can be ignored. | ||
|
||
> [!IMPORTANT] | ||
> When a finding is ignored, it is removed from consideration for threshold checks, but it's not discarded. The annotation created by the plugin adds details to the results, giving high visibility on the configured behaviour. | ||
|
||
## Rendering | ||
|
||
The summary counts at the top show the number of ignored findings: | ||
|
||
<img src="img/summary-counts.png" alt="summary counts" width="60%"> | ||
|
||
Ignored findings are separated from the main list and shown at the bottom: | ||
|
||
<img src="img/ignore-finding-list.png" alt="ignored finding list" width="60%"> | ||
|
||
If a reason for ignoring a finding is provided, it's made available by expanding the Until date: | ||
|
||
<img src="img/ignore-reason.png" alt="ignored reason" width="60%"> |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package finding | ||
|
||
import ( | ||
"slices" | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go-v2/aws" | ||
"github.com/aws/aws-sdk-go-v2/service/ecr/types" | ||
"github.com/cultureamp/ecrscanresults/findingconfig" | ||
) | ||
|
||
type Detail struct { | ||
// The name associated with the finding, usually a CVE number. | ||
Name string | ||
URI string | ||
Description string | ||
Severity types.FindingSeverity | ||
|
||
PackageName string | ||
PackageVersion string | ||
CVSS2Score string | ||
CVSS2Vector string | ||
|
||
Ignore *findingconfig.Ignore | ||
} | ||
|
||
type SeverityCount struct { | ||
// Included is the number of findings that count towards the threshold for this severity. | ||
Included int32 | ||
|
||
// Ignored is the number of findings that were ignored for the purposes of the threshold. | ||
Ignored int32 | ||
} | ||
|
||
type Summary struct { | ||
// the counts by threshold, taking ignore configuration into account | ||
Counts map[types.FindingSeverity]SeverityCount | ||
|
||
Details []Detail | ||
|
||
// the set of finding IDs that have been ignored by configuration | ||
Ignored []Detail | ||
|
||
// The time of the last completed image scan. | ||
ImageScanCompletedAt *time.Time | ||
|
||
// The time when the vulnerability data was last scanned. | ||
VulnerabilitySourceUpdatedAt *time.Time | ||
} | ||
|
||
func (s *Summary) addDetail(d Detail) { | ||
s.Details = append(s.Details, d) | ||
s.updateCount(d.Severity, SeverityCount{Included: 1}) | ||
} | ||
|
||
func (s *Summary) addIgnored(d Detail) { | ||
s.Ignored = append(s.Ignored, d) | ||
s.updateCount(d.Severity, SeverityCount{Ignored: 1}) | ||
} | ||
|
||
func (s *Summary) updateCount(severity types.FindingSeverity, updateBy SeverityCount) { | ||
counts := s.Counts[severity] | ||
|
||
counts.Ignored += updateBy.Ignored | ||
counts.Included += updateBy.Included | ||
|
||
s.Counts[severity] = counts | ||
} | ||
|
||
func newSummary() Summary { | ||
return Summary{ | ||
Counts: map[types.FindingSeverity]SeverityCount{ | ||
"CRITICAL": {}, | ||
"HIGH": {}, | ||
}, | ||
Details: []Detail{}, | ||
Ignored: []Detail{}, | ||
} | ||
} | ||
|
||
func Summarize(findings *types.ImageScanFindings, ignoreConfig []findingconfig.Ignore) Summary { | ||
summary := newSummary() | ||
|
||
summary.ImageScanCompletedAt = findings.ImageScanCompletedAt | ||
summary.VulnerabilitySourceUpdatedAt = findings.VulnerabilitySourceUpdatedAt | ||
|
||
for _, f := range findings.Findings { | ||
detail := findingToDetail(f) | ||
|
||
index := slices.IndexFunc(ignoreConfig, func(ignore findingconfig.Ignore) bool { | ||
return ignore.ID == detail.Name | ||
}) | ||
|
||
if index >= 0 { | ||
detail.Ignore = &ignoreConfig[index] | ||
summary.addIgnored(detail) | ||
} else { | ||
summary.addDetail(detail) | ||
} | ||
} | ||
|
||
return summary | ||
} | ||
|
||
func findingToDetail(finding types.ImageScanFinding) Detail { | ||
return Detail{ | ||
Name: aws.ToString(finding.Name), | ||
URI: aws.ToString(finding.Uri), | ||
Description: aws.ToString(finding.Description), | ||
Severity: finding.Severity, | ||
PackageName: findingAttributeValue(finding, "package_name"), | ||
PackageVersion: findingAttributeValue(finding, "package_version"), | ||
CVSS2Score: findingAttributeValue(finding, "CVSS2_SCORE"), | ||
CVSS2Vector: findingAttributeValue(finding, "CVSS2_VECTOR"), | ||
} | ||
} | ||
|
||
func findingAttributeValue(finding types.ImageScanFinding, name string) string { | ||
for _, a := range finding.Attributes { | ||
if aws.ToString(a.Key) == name { | ||
return aws.ToString(a.Value) | ||
} | ||
} | ||
return "" | ||
} |
Oops, something went wrong.