Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions auto-discovery/cloud-aws/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,14 @@ This means the AWS AutoDiscovery should either be free or cheaper than $1/month
| config.aws | object | `{"queueUrl":"","region":""}` | settings to connect to AWS and receive the updates |
| config.aws.queueUrl | string | `""` | url of the SQS queue which receives the state changes. Can be overridden by setting the SQS_QUEUE_URL environment variable. |
| config.aws.region | string | `""` | aws region to connect to. Can be overridden by setting the AWS_REGION environment variable. |
| config.kubernetes | object | `{"scanConfigs":[{"annotations":{},"hookSelector":{},"labels":{},"name":"trivy","parameters":["{{ .ImageID }}"],"repeatInterval":"168h","scanType":"trivy-image"},{"annotations":{},"hookSelector":{},"labels":{},"name":"trivy-sbom","parameters":["{{ .ImageID }}"],"repeatInterval":"168h","scanType":"trivy-sbom-image"}]}` | settings to configure how scans get created in kubernetes |
| config.kubernetes | object | `{"scanConfigs":[{"annotations":{},"hookSelector":{},"labels":{},"name":"trivy","parameters":["{{ .ImageID }}"],"repeatInterval":"168h","scanType":"trivy-image"},{"annotations":{"dependencytrack.securecodebox.io/project-name":"{{ .Image.ShortName }}","dependencytrack.securecodebox.io/project-version":"{{ .Image.Version }}"},"hookSelector":{},"labels":{},"name":"trivy-sbom","parameters":["{{ .ImageID }}"],"repeatInterval":"168h","scanType":"trivy-sbom-image"}]}` | settings to configure how scans get created in kubernetes |
| config.kubernetes.scanConfigs[0].annotations | object | `{}` | annotations to be added to the scans started by the auto-discovery, all annotation values support templating |
| config.kubernetes.scanConfigs[0].hookSelector | object | `{}` | hookSelector allows to specify a LabelSelector with which the hooks are selected, see: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors Both matchLabels and matchExpressions are supported. All values in the matchLabels map support templating. MatchExpressions support templating in the `key` field and in every entry in the `values` list. If a value in the list renders to an empty string it is removed from the list. |
| config.kubernetes.scanConfigs[0].labels | object | `{}` | labels to be added to the scans started by the auto-discovery, all label values support templating |
| config.kubernetes.scanConfigs[0].name | string | `"trivy"` | unique name to distinguish scans |
| config.kubernetes.scanConfigs[0].parameters | list | `["{{ .ImageID }}"]` | parameters used for the scans created by the containerAutoDiscovery, all parameters support templating |
| config.kubernetes.scanConfigs[0].repeatInterval | string | `"168h"` | interval in which scans are automatically repeated. If the target is updated (meaning a new image revision is deployed) the scan will repeated beforehand and the interval is reset. |
| config.kubernetes.scanConfigs[1].annotations | object | `{}` | annotations to be added to the scans started by the auto-discovery, all annotation values support templating |
| config.kubernetes.scanConfigs[1].annotations | object | `{"dependencytrack.securecodebox.io/project-name":"{{ .Image.ShortName }}","dependencytrack.securecodebox.io/project-version":"{{ .Image.Version }}"}` | annotations to be added to the scans started by the auto-discovery, all annotation values support templating |
| config.kubernetes.scanConfigs[1].hookSelector | object | `{}` | hookSelector allows to specify a LabelSelector with which the hooks are selected, see: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors Both matchLabels and matchExpressions are supported. All values in the matchLabels map support templating. MatchExpressions support templating in the `key` field and in every entry in the `values` list. If a value in the list renders to an empty string it is removed from the list. |
| config.kubernetes.scanConfigs[1].labels | object | `{}` | labels to be added to the scans started by the auto-discovery, all label values support templating |
| config.kubernetes.scanConfigs[1].name | string | `"trivy-sbom"` | unique name to distinguish scans |
Expand Down
4 changes: 2 additions & 2 deletions auto-discovery/cloud-aws/cmd/service/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ var _ = Describe("Integration tests", func() {
juiceShopScanName2 = juiceShopScanName2[:62]

juiceShopScanGoTemplate := scanGoTemplate{
map[string]string{"testAnnotation": "VeryUniqueId"},
map[string]string{"testAnnotation": "bkimminich/juice-shop"},
map[string]string{
"testLabel": "VeryUniqueId",
"app.kubernetes.io/managed-by": "securecodebox-autodiscovery",
Expand All @@ -57,7 +57,7 @@ var _ = Describe("Integration tests", func() {
helloWorldScanName2 = helloWorldScanName2[:62]

helloWorldScanGoTemplate := scanGoTemplate{
map[string]string{"testAnnotation": "ExtremelyUniqueId"},
map[string]string{"testAnnotation": "library/hello-world"},
map[string]string{
"testLabel": "ExtremelyUniqueId",
"app.kubernetes.io/managed-by": "securecodebox-autodiscovery",
Expand Down
4 changes: 2 additions & 2 deletions auto-discovery/cloud-aws/cmd/service/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ var _ = BeforeSuite(func() {
{
Name: "test-scan",
RepeatInterval: metav1.Duration{Duration: time.Hour},
Annotations: map[string]string{"testAnnotation": "{{ .Target.Id }}"},
Annotations: map[string]string{"testAnnotation": "{{ .Image.ShortName }}"},
Labels: map[string]string{"testLabel": "{{ .Target.Id }}"},
Parameters: []string{"{{ .ImageID }}"},
ScanType: "trivy-sbom-image",
Expand All @@ -111,7 +111,7 @@ var _ = BeforeSuite(func() {
{
Name: "test-scan-two",
RepeatInterval: metav1.Duration{Duration: time.Hour},
Annotations: map[string]string{"testAnnotation": "{{ .Target.Id }}"},
Annotations: map[string]string{"testAnnotation": "{{ .Image.ShortName }}"},
Labels: map[string]string{"testLabel": "{{ .Target.Id }}"},
Parameters: []string{"{{ .ImageID }}"},
ScanType: "trivy-sbom-image",
Expand Down
4 changes: 2 additions & 2 deletions auto-discovery/cloud-aws/docs/README.ArtifactHub.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,14 @@ This means the AWS AutoDiscovery should either be free or cheaper than $1/month
| config.aws | object | `{"queueUrl":"","region":""}` | settings to connect to AWS and receive the updates |
| config.aws.queueUrl | string | `""` | url of the SQS queue which receives the state changes. Can be overridden by setting the SQS_QUEUE_URL environment variable. |
| config.aws.region | string | `""` | aws region to connect to. Can be overridden by setting the AWS_REGION environment variable. |
| config.kubernetes | object | `{"scanConfigs":[{"annotations":{},"hookSelector":{},"labels":{},"name":"trivy","parameters":["{{ .ImageID }}"],"repeatInterval":"168h","scanType":"trivy-image"},{"annotations":{},"hookSelector":{},"labels":{},"name":"trivy-sbom","parameters":["{{ .ImageID }}"],"repeatInterval":"168h","scanType":"trivy-sbom-image"}]}` | settings to configure how scans get created in kubernetes |
| config.kubernetes | object | `{"scanConfigs":[{"annotations":{},"hookSelector":{},"labels":{},"name":"trivy","parameters":["{{ .ImageID }}"],"repeatInterval":"168h","scanType":"trivy-image"},{"annotations":{"dependencytrack.securecodebox.io/project-name":"{{ .Image.ShortName }}","dependencytrack.securecodebox.io/project-version":"{{ .Image.Version }}"},"hookSelector":{},"labels":{},"name":"trivy-sbom","parameters":["{{ .ImageID }}"],"repeatInterval":"168h","scanType":"trivy-sbom-image"}]}` | settings to configure how scans get created in kubernetes |
| config.kubernetes.scanConfigs[0].annotations | object | `{}` | annotations to be added to the scans started by the auto-discovery, all annotation values support templating |
| config.kubernetes.scanConfigs[0].hookSelector | object | `{}` | hookSelector allows to specify a LabelSelector with which the hooks are selected, see: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors Both matchLabels and matchExpressions are supported. All values in the matchLabels map support templating. MatchExpressions support templating in the `key` field and in every entry in the `values` list. If a value in the list renders to an empty string it is removed from the list. |
| config.kubernetes.scanConfigs[0].labels | object | `{}` | labels to be added to the scans started by the auto-discovery, all label values support templating |
| config.kubernetes.scanConfigs[0].name | string | `"trivy"` | unique name to distinguish scans |
| config.kubernetes.scanConfigs[0].parameters | list | `["{{ .ImageID }}"]` | parameters used for the scans created by the containerAutoDiscovery, all parameters support templating |
| config.kubernetes.scanConfigs[0].repeatInterval | string | `"168h"` | interval in which scans are automatically repeated. If the target is updated (meaning a new image revision is deployed) the scan will repeated beforehand and the interval is reset. |
| config.kubernetes.scanConfigs[1].annotations | object | `{}` | annotations to be added to the scans started by the auto-discovery, all annotation values support templating |
| config.kubernetes.scanConfigs[1].annotations | object | `{"dependencytrack.securecodebox.io/project-name":"{{ .Image.ShortName }}","dependencytrack.securecodebox.io/project-version":"{{ .Image.Version }}"}` | annotations to be added to the scans started by the auto-discovery, all annotation values support templating |
| config.kubernetes.scanConfigs[1].hookSelector | object | `{}` | hookSelector allows to specify a LabelSelector with which the hooks are selected, see: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors Both matchLabels and matchExpressions are supported. All values in the matchLabels map support templating. MatchExpressions support templating in the `key` field and in every entry in the `values` list. If a value in the list renders to an empty string it is removed from the list. |
| config.kubernetes.scanConfigs[1].labels | object | `{}` | labels to be added to the scans started by the auto-discovery, all label values support templating |
| config.kubernetes.scanConfigs[1].name | string | `"trivy-sbom"` | unique name to distinguish scans |
Expand Down
54 changes: 53 additions & 1 deletion auto-discovery/cloud-aws/pkg/kubernetes/docker_reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ type ImageInfo struct {
parsed *dockerparser.Reference
}

// Image details for templating that would otherwise not be accessible because you need to call functions
type ImageDetails struct {
Id string
Name string
Digest string
Version string
Registry string
Repository string
ShortName string
}

// Use dockerparser to normalize the image reference and allow easy access to the properties
func (image *ImageInfo) normalize() error {
// To prevent misdetection of containers using the same digest but different tags (i.e. none
Expand All @@ -33,8 +44,21 @@ func (image *ImageInfo) normalize() error {
return nil
}

// Create object with all the values that would only be accessible by calling functions
func (image *ImageInfo) details() ImageDetails {
return ImageDetails{
Id: image.reference(),
Name: image.Name,
Digest: image.Digest,
Version: image.version(),
Registry: image.registry(),
Repository: image.repository(),
ShortName: image.shortName(),
}
}

// Get a short, representative name for the image
func (image *ImageInfo) appName() string {
func (image *ImageInfo) shortName() string {
// If the image is parsed or parsing works use library function
if image.parsed != nil || image.normalize() == nil {
return image.parsed.ShortName()
Expand Down Expand Up @@ -121,3 +145,31 @@ func (image *ImageInfo) reference() string {
return image.Name + "@" + image.Digest
}
}

// Get the registry of this image, mirrors the dockerparser function
func (image *ImageInfo) registry() string {
// If the image is parsed or parsing works use library function
if image.parsed != nil || image.normalize() == nil {
return image.parsed.Registry()
}

// Parsing failed, try to salvage this somehow by returning what we have
// If name contains a port, domain or localhost that is the registry
split := strings.Split(image.Name, "/")
if strings.Contains(split[0], ":") || strings.Contains(split[0], ".") || split[0] == "localhost" {
return split[0]
} else {
return "docker.io"
}
}

// Get the repository of this image, mirrors the dockerparser function
func (image *ImageInfo) repository() string {
// If the image is parsed or parsing works use library function
if image.parsed != nil || image.normalize() == nil {
return image.parsed.Repository()
}

// Parsing failed, try to salvage this somehow by returning what we have
return image.registry() + "/" + image.shortName()
}
4 changes: 3 additions & 1 deletion auto-discovery/cloud-aws/pkg/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type ContainerAutoDiscoveryTemplateArgs struct {
Config config.AutoDiscoveryConfig
ScanConfig configv1.ScanConfig
Target ContainerInfo
Image ImageDetails
ImageID string
}

Expand Down Expand Up @@ -207,6 +208,7 @@ func getScheduledScanForRequest(req Request, cfg *config.AutoDiscoveryConfig, sc
Config: *cfg,
ScanConfig: scanConfig,
Target: req.Container,
Image: req.Container.Image.details(),
ImageID: req.Container.Image.reference(),
}
scanSpec := util.GenerateScanSpec(scanConfig, templateArgs)
Expand All @@ -227,7 +229,7 @@ func getScanName(req Request, name string) string {
// adapted from the kubernetes container autodiscovery
// function builds string like: _appName_-_customScanName_-at-_imageID_HASH_ eg: nginx-myTrivyScan-at-0123456789

appName := req.Container.Image.appName()
appName := req.Container.Image.shortName()
hash := req.Container.Image.hash()

// cutoff appname if it is longer than 20 chars
Expand Down
4 changes: 3 additions & 1 deletion auto-discovery/cloud-aws/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ config:
# -- labels to be added to the scans started by the auto-discovery, all label values support templating
labels: {}
# -- annotations to be added to the scans started by the auto-discovery, all annotation values support templating
annotations: {}
annotations:
dependencytrack.securecodebox.io/project-name: "{{ .Image.ShortName }}"
dependencytrack.securecodebox.io/project-version: "{{ .Image.Version }}"
# -- hookSelector allows to specify a LabelSelector with which the hooks are selected, see: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
# Both matchLabels and matchExpressions are supported.
# All values in the matchLabels map support templating.
Expand Down
7 changes: 7 additions & 0 deletions hooks/persistence-dependencytrack/.helm-docs.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ SBOMs are imported for a project in Dependency-Track.
To avoid configuring all of them by hand first and assigning projects to scans somehow, the hook automatically detects name and version from the scan and then creates Dependency-Track projects if they do not exist yet.
This requires either the `PORTFOLIO_MANAGEMENT` or `PROJECT_CREATION_UPLOAD` permission for the API key which gets used by the hook (or rather for the team the key is defined for).

The hook extracts name and version from the reference of the scanned docker image.
For more fine grained control how the projects are matched, you can configure the name and version as annotations to the scan.

| Scan Annotation | Description | Default if not set |
| -------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------- |
| `dependencytrack.securecodebox.io/project-name` | Name of the Project | Repository Name of the Docker Image |
| `dependencytrack.securecodebox.io/project-version` | Version of the Project | Image Tag if avialable, otherwise Image Digest if available, otherwise `latest` |
{{- end }}

{{- define "extra.scannerLinksSection" -}}
Expand Down
8 changes: 8 additions & 0 deletions hooks/persistence-dependencytrack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ SBOMs are imported for a project in Dependency-Track.
To avoid configuring all of them by hand first and assigning projects to scans somehow, the hook automatically detects name and version from the scan and then creates Dependency-Track projects if they do not exist yet.
This requires either the `PORTFOLIO_MANAGEMENT` or `PROJECT_CREATION_UPLOAD` permission for the API key which gets used by the hook (or rather for the team the key is defined for).

The hook extracts name and version from the reference of the scanned docker image.
For more fine grained control how the projects are matched, you can configure the name and version as annotations to the scan.

| Scan Annotation | Description | Default if not set |
| -------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------- |
| `dependencytrack.securecodebox.io/project-name` | Name of the Project | Repository Name of the Docker Image |
| `dependencytrack.securecodebox.io/project-version` | Version of the Project | Image Tag if avialable, otherwise Image Digest if available, otherwise `latest` |

## Values

| Key | Type | Default | Description |
Expand Down
18 changes: 13 additions & 5 deletions hooks/persistence-dependencytrack/hook/hook.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,17 @@ async function handle({

console.log(`Persisting SBOM for ${result.metadata.component.name} to Dependency-Track`);

// Get the project name and version from the name attribute of the main component
// This might be a bit brittle, but there is not really a better way to get this information
// Neither Trivy's nor Syft's SBOM contains a useful version attribute (none or sha256)
// Try to get the project name and version from annotations
let name, version
if (scan?.metadata?.annotations) {
name = scan.metadata.annotations["dependencytrack.securecodebox.io/project-name"]
version = scan.metadata.annotations["dependencytrack.securecodebox.io/project-version"]
}

// Get the project name and version from the name attribute of the main component if the
// annotations are missing. This might be a bit brittle, but there is not really a better way to
// get this information in that case, neither Trivy's nor Syft's SBOM contains a useful version
// attribute (none or sha256)

// Get the components of a docker image reference, the regex is a direct JavaScript adaption of
// the official Go-implementation available at https://github.com/distribution/reference/blob/main/regexp.go
Expand All @@ -45,8 +53,8 @@ async function handle({
'(?:@(?<digest>[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][0-9A-Fa-f]{32,}))?$',
].join(''));
const groups = imageRegex.exec(result.metadata.component.name).groups
const name = groups.name
const version = groups.tag || groups.digest || "latest"
name = name || groups.name
version = version || groups.tag || groups.digest || "latest"

// The POST endpoint expects multipart/form-data
// Alternatively the PUT endpoint could be used, which requires base64-encoding the SBOM
Expand Down
6 changes: 6 additions & 0 deletions hooks/persistence-dependencytrack/hook/hook.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ test("should send a post request to the url when fired", async () => {
metadata: {
uid: "69e71358-bb01-425b-9bde-e45653605490",
name: "demo-sbom",
annotations: {
"dependencytrack.securecodebox.io/project-name": "Hello World Container",
"dependencytrack.securecodebox.io/project-version": "latest and greatest"
}
},
status: {
rawResultType: "sbom-cyclonedx"
Expand All @@ -110,6 +114,8 @@ test("should send a post request to the url when fired", async () => {
}));

expect(fetch.mock.calls[0][1].body.get("bom")).toBe(JSON.stringify(result));
expect(fetch.mock.calls[0][1].body.get("projectName")).toBe("Hello World Container");
expect(fetch.mock.calls[0][1].body.get("projectVersion")).toBe("latest and greatest");
});

// Make sure that the crazy regex to parse the reference parts actually works
Expand Down