mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-21 23:00:42 -08:00
feat: add secret scanning (#1901)
Co-authored-by: VaismanLior <97836016+VaismanLior@users.noreply.github.com> Co-authored-by: AMF <work@afdesk.com>
This commit is contained in:
141
docs/docs/secret/configuration.md
Normal file
141
docs/docs/secret/configuration.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Configuration
|
||||
Trivy tries to load `trivy-secret.yaml` in the current directory by default.
|
||||
If the file doesn't exist, only builtin rules are used.
|
||||
You can customize the config file name via the `--secret-config` flag.
|
||||
|
||||
## Custom Rules
|
||||
Trivy allows defining custom rules. You can see an example.
|
||||
|
||||
``` yaml
|
||||
rules:
|
||||
- id: rule1
|
||||
category: general
|
||||
title: Generic Rule
|
||||
severity: HIGH
|
||||
path:
|
||||
- .*\.sh
|
||||
keywords:
|
||||
- secret
|
||||
regex: (?i)(?P<key>(secret))(=|:).{0,5}['"](?P<secret>[0-9a-zA-Z\-_=]{8,64})['"]
|
||||
secret-group-name: secret
|
||||
allow-rules:
|
||||
- id: skip-text
|
||||
description: skip text files
|
||||
path: .*\.txt
|
||||
```
|
||||
|
||||
`id` (required)
|
||||
: - Unique identifier for this rule.
|
||||
|
||||
`category` (required)
|
||||
: - String used for metadata and reporting purposes.
|
||||
|
||||
`title` (required)
|
||||
: - Short human-readable title of the rule.
|
||||
|
||||
`severity` (required)
|
||||
: - How critical this rule is.
|
||||
- Allowed values:
|
||||
- CRITICAL
|
||||
- HIGH
|
||||
- MEDIUM
|
||||
- LOW
|
||||
|
||||
`regex` (required)
|
||||
: - Golang regular expression used to detect secrets.
|
||||
|
||||
`path` (optional)
|
||||
: - Golang regular expression used to match paths.
|
||||
|
||||
`keywords` (optional, recommended)
|
||||
: - Keywords are used for pre-regex check filtering.
|
||||
- Rules that contain keywords will perform a quick string compare check to make sure the keyword(s) are in the content being scanned.
|
||||
- Ideally these values should either be part of the identifier or unique strings specific to the rule's regex.
|
||||
- It is recommended to define for better performance.
|
||||
|
||||
`allow-rules` (optional)
|
||||
: - Allow rules for a single rule to reduce false positives with known secrets.
|
||||
- The details are below.
|
||||
|
||||
## Allow Rules
|
||||
If the detected secret is matched with the specified `regex`, then that secret will be skipped and not detected.
|
||||
The same logic applies for `path`.
|
||||
|
||||
`allow-rules` can be defined globally and per each rule. The fields are the same.
|
||||
|
||||
``` yaml
|
||||
rules:
|
||||
- id: rule1
|
||||
category: general
|
||||
title: Generic Rule
|
||||
severity: HIGH
|
||||
regex: (?i)(?P<key>(secret))(=|:).{0,5}['"](?P<secret>[0-9a-zA-Z\-_=]{8,64})['"]
|
||||
allow-rules:
|
||||
- id: skip-text
|
||||
description: skip text files
|
||||
path: .*\.txt
|
||||
allow-rules:
|
||||
- id: social-security-number
|
||||
description: skip social security number
|
||||
regex: 219-09-9999
|
||||
```
|
||||
|
||||
|
||||
`id` (required)
|
||||
: - Unique identifier for this allow rule.
|
||||
|
||||
`description` (optional)
|
||||
: - Short human-readable description of this allow rule.
|
||||
|
||||
`regex` (optional)
|
||||
: - Golang regular expression used to allow detected secrets.
|
||||
- `regex` or `path` must be specified.
|
||||
|
||||
`path` (optional)
|
||||
: - Golang regular expression used to allow matched paths.
|
||||
- `regex` or `path` must be specified.
|
||||
|
||||
## Disable Rules
|
||||
Trivy offers builtin rules and allow rules, but you may want to disable some of them.
|
||||
For example, you don't use Slack, so Slack doesn't have to be scanned.
|
||||
You can specify `slack-access-token` and `slack-web-hook` in `disable-rules` so that those rules will be disabled for less false positives.
|
||||
|
||||
Markdown files are ignored by default, but you may want to scan markdown files as well.
|
||||
You can disable the allow list by adding `markdown` to `disable-allow-rules`.
|
||||
|
||||
You can see a full list of rule IDs [here][builtin]. Allow rule IDs are below in the file.
|
||||
|
||||
``` yaml
|
||||
disable-rules:
|
||||
- slack-access-token
|
||||
- slack-web-hook
|
||||
disable-allow-rules:
|
||||
- markdown
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
``` yaml
|
||||
$ cat trivy-secret.yaml
|
||||
rules:
|
||||
- id: rule1
|
||||
category: general
|
||||
title: Generic Rule
|
||||
severity: HIGH
|
||||
regex: (?i)(?P<key>(secret))(=|:).{0,5}['"](?P<secret>[0-9a-zA-Z\-_=]{8,64})['"]
|
||||
allow-rules:
|
||||
- id: skip-text
|
||||
description: skip text files
|
||||
path: .*\.txt
|
||||
allow-rules:
|
||||
- id: social-security-number
|
||||
description: skip social security number
|
||||
regex: 219-09-9999
|
||||
disable-rules:
|
||||
- slack-access-token
|
||||
- slack-web-hook
|
||||
disable-allow-rules:
|
||||
- markdown
|
||||
```
|
||||
|
||||
[builtin]: https://github.com/aquasecurity/fanal/blob/main/secret/builtin.go
|
||||
100
docs/docs/secret/scanning.md
Normal file
100
docs/docs/secret/scanning.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Secret Scanning
|
||||
|
||||
Trivy scans any container image, filesystem and git repository to detect exposed secrets like passwords, api keys, and tokens.
|
||||
Secret scanning is enabled by default.
|
||||
|
||||
Trivy will scan every plaintext file, according to builtin rules or configuration. There are plenty of builtin rules:
|
||||
|
||||
- AWS access key
|
||||
- GCP service account
|
||||
- GitHub personal access token
|
||||
- GitLab personal access token
|
||||
- Slack access token
|
||||
- etc.
|
||||
|
||||
You can see a full list of builtin rules [here][builtin].
|
||||
|
||||
|
||||
## Quick start
|
||||
This section shows how to scan secrets in container image and filesystem. Other subcommands should be the same.
|
||||
|
||||
### Container image
|
||||
Specify an image name.
|
||||
|
||||
``` shell
|
||||
$ trivy image myimage:1.0.0
|
||||
2022-04-21T18:56:44.099+0300 INFO Detected OS: alpine
|
||||
2022-04-21T18:56:44.099+0300 INFO Detecting Alpine vulnerabilities...
|
||||
2022-04-21T18:56:44.101+0300 INFO Number of language-specific files: 0
|
||||
|
||||
myimage:1.0.0 (alpine 3.15.0)
|
||||
=============================
|
||||
Total: 6 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 2)
|
||||
|
||||
+--------------+------------------+----------+-------------------+---------------+---------------------------------------+
|
||||
| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE |
|
||||
+--------------+------------------+----------+-------------------+---------------+---------------------------------------+
|
||||
| busybox | CVE-2022-28391 | CRITICAL | 1.34.1-r3 | 1.34.1-r5 | CVE-2022-28391 affecting |
|
||||
| | | | | | package busybox 1.35.0 |
|
||||
| | | | | | -->avd.aquasec.com/nvd/cve-2022-28391 |
|
||||
+--------------+------------------| |-------------------+---------------+---------------------------------------+
|
||||
| ssl_client | CVE-2022-28391 | | 1.34.1-r3 | 1.34.1-r5 | CVE-2022-28391 affecting |
|
||||
| | | | | | package busybox 1.35.0 |
|
||||
| | | | | | -->avd.aquasec.com/nvd/cve-2022-28391 |
|
||||
+--------------+------------------+----------+-------------------+---------------+---------------------------------------+
|
||||
|
||||
app/secret.sh (secrets)
|
||||
=======================
|
||||
Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 1)
|
||||
|
||||
+----------+-------------------+----------+---------+--------------------------------+
|
||||
| CATEGORY | DESCRIPTION | SEVERITY | LINE NO | MATCH |
|
||||
+----------+-------------------+----------+---------+--------------------------------+
|
||||
| AWS | AWS Access Key ID | CRITICAL | 10 | export AWS_ACCESS_KEY_ID=***** |
|
||||
+----------+-------------------+----------+---------+--------------------------------+
|
||||
```
|
||||
|
||||
|
||||
!!! tip
|
||||
Trivy tries to detect a base image and skip those layers for secret scanning.
|
||||
A base image usually contains a lot of files and makes secret scanning much slower.
|
||||
If a secret is not detected properly, you can see base layers with the `--debug` flag.
|
||||
|
||||
### Filesystem
|
||||
|
||||
``` shell
|
||||
$ trivy fs /path/to/your_project
|
||||
...(snip)...
|
||||
|
||||
certs/key.pem (secrets)
|
||||
========================
|
||||
Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 1, CRITICAL: 0)
|
||||
|
||||
+----------------------+------------------------+----------+---------+---------------------------------+
|
||||
| CATEGORY | DESCRIPTION | SEVERITY | LINE NO | MATCH |
|
||||
+----------------------+------------------------+----------+---------+---------------------------------+
|
||||
| AsymmetricPrivateKey | Asymmetric Private Key | HIGH | 1 | -----BEGIN RSA PRIVATE KEY----- |
|
||||
+----------------------+------------------------+----------+---------+---------------------------------+
|
||||
```
|
||||
|
||||
|
||||
!!! tip
|
||||
Your project may have some secrets for testing. You can skip them with `--skip-dirs` or `--skip-files`.
|
||||
We would recommend specifying these options so that the secret scanning can be faster if those files don't need to be scanned.
|
||||
Also, you can specify paths to be allowed in a configuration file. See the detail [here][configuration].
|
||||
|
||||
## Configuration
|
||||
Trivy has a set of builtin rules for secret scanning, which can be extended or modified by a configuration file.
|
||||
|
||||
If you don't need secret scanning, you can disable it via the `--security-checks` flag.
|
||||
|
||||
```shell
|
||||
$ trivy image --security-checks vuln alpine:3.15
|
||||
```
|
||||
|
||||
## Credit
|
||||
This feature is inspired by [gitleaks][gitleaks].
|
||||
|
||||
[builtin]: https://github.com/aquasecurity/fanal/blob/main/secret/builtin.go
|
||||
[configuration]: ./configuration.md
|
||||
[gitleaks]: https://github.com/zricethezav/gitleaks
|
||||
@@ -174,7 +174,7 @@ The same image is hosted on [Amazon ECR Public][ecr] as well.
|
||||
docker pull public.ecr.aws/aquasecurity/trivy:{{ git.tag[1:] }}
|
||||
```
|
||||
## Helm
|
||||
### Installing from the the Aqua Chart Repository
|
||||
### Installing from the Aqua Chart Repository
|
||||
|
||||
```
|
||||
helm repo add aquasecurity https://aquasecurity.github.io/helm-charts/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Overview
|
||||
|
||||
Trivy detects two types of security issues:
|
||||
Trivy detects three types of security issues:
|
||||
|
||||
- [Vulnerabilities][vuln]
|
||||
- [OS packages][os] (Alpine, Red Hat Universal Base Image, Red Hat Enterprise Linux, CentOS, AlmaLinux, Rocky Linux, CBL-Mariner, Oracle Linux, Debian, Ubuntu, Amazon Linux, openSUSE Leap, SUSE Enterprise Linux, Photon OS and Distroless)
|
||||
@@ -11,6 +11,11 @@ Trivy detects two types of security issues:
|
||||
- Terraform
|
||||
- CloudFormation
|
||||
- more coming soon
|
||||
- [Secrets][secret]
|
||||
- AWS access key
|
||||
- GCP service account
|
||||
- GitHub personal access token
|
||||
- etc.
|
||||
|
||||
Trivy can scan three different artifacts:
|
||||
|
||||
@@ -27,6 +32,8 @@ See [Integrations][integrations] for details.
|
||||
|
||||
[misconf]: ../docs/misconfiguration/index.md
|
||||
|
||||
[secret]: ../docs/secret/scanning.md
|
||||
|
||||
[container]: ../docs/vulnerability/scanning/image.md
|
||||
[rootfs]: ../docs/vulnerability/scanning/rootfs.md
|
||||
[filesystem]: ../docs/vulnerability/scanning/filesystem.md
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Quick Start
|
||||
|
||||
## Scan image for vulnerabilities
|
||||
## Scan image for vulnerabilities and secrets
|
||||
|
||||
Simply specify an image name (and a tag).
|
||||
|
||||
@@ -10,32 +10,40 @@ $ trivy image [YOUR_IMAGE_NAME]
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
$ trivy image python:3.4-alpine
|
||||
```
|
||||
``` shell
|
||||
$ trivy image myimage:1.0.0
|
||||
2022-04-21T18:56:44.099+0300 INFO Detected OS: alpine
|
||||
2022-04-21T18:56:44.099+0300 INFO Detecting Alpine vulnerabilities...
|
||||
2022-04-21T18:56:44.101+0300 INFO Number of language-specific files: 0
|
||||
|
||||
<details>
|
||||
<summary>Result</summary>
|
||||
myimage:1.0.0 (alpine 3.15.0)
|
||||
=============================
|
||||
Total: 6 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 2)
|
||||
|
||||
```
|
||||
2019-05-16T01:20:43.180+0900 INFO Updating vulnerability database...
|
||||
2019-05-16T01:20:53.029+0900 INFO Detecting Alpine vulnerabilities...
|
||||
|
||||
python:3.4-alpine3.9 (alpine 3.9.2)
|
||||
===================================
|
||||
Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 0, CRITICAL: 0)
|
||||
|
||||
+---------+------------------+----------+-------------------+---------------+--------------------------------+
|
||||
+--------------+------------------+----------+-------------------+---------------+---------------------------------------+
|
||||
| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE |
|
||||
+---------+------------------+----------+-------------------+---------------+--------------------------------+
|
||||
| openssl | CVE-2019-1543 | MEDIUM | 1.1.1a-r1 | 1.1.1b-r1 | openssl: ChaCha20-Poly1305 |
|
||||
| | | | | | with long nonces |
|
||||
+---------+------------------+----------+-------------------+---------------+--------------------------------+
|
||||
+--------------+------------------+----------+-------------------+---------------+---------------------------------------+
|
||||
| busybox | CVE-2022-28391 | CRITICAL | 1.34.1-r3 | 1.34.1-r5 | CVE-2022-28391 affecting |
|
||||
| | | | | | package busybox 1.35.0 |
|
||||
| | | | | | -->avd.aquasec.com/nvd/cve-2022-28391 |
|
||||
+--------------+------------------| |-------------------+---------------+---------------------------------------+
|
||||
| ssl_client | CVE-2022-28391 | | 1.34.1-r3 | 1.34.1-r5 | CVE-2022-28391 affecting |
|
||||
| | | | | | package busybox 1.35.0 |
|
||||
| | | | | | -->avd.aquasec.com/nvd/cve-2022-28391 |
|
||||
+--------------+------------------+----------+-------------------+---------------+---------------------------------------+
|
||||
|
||||
app/deploy.sh (secrets)
|
||||
=======================
|
||||
Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 1)
|
||||
|
||||
+----------+-------------------+----------+---------+--------------------------------+
|
||||
| CATEGORY | DESCRIPTION | SEVERITY | LINE NO | MATCH |
|
||||
+----------+-------------------+----------+---------+--------------------------------+
|
||||
| AWS | AWS Access Key ID | CRITICAL | 10 | export AWS_ACCESS_KEY_ID=***** |
|
||||
+----------+-------------------+----------+---------+--------------------------------+
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
For more details, see [here][vulnerability].
|
||||
For more details, see [vulnerability][vulnerability] and [secret][secret] pages.
|
||||
|
||||
## Scan directory for misconfigurations
|
||||
|
||||
@@ -47,16 +55,10 @@ $ trivy config [YOUR_IAC_DIR]
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
``` shell
|
||||
$ ls build/
|
||||
Dockerfile
|
||||
$ trivy config ./build
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Result</summary>
|
||||
|
||||
```
|
||||
2021-07-09T10:06:29.188+0300 INFO Need to update the built-in policies
|
||||
2021-07-09T10:06:29.188+0300 INFO Downloading the built-in policies...
|
||||
2021-07-09T10:06:30.520+0300 INFO Detected config files: 1
|
||||
@@ -75,9 +77,8 @@ Failures: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 1, CRITICAL: 0)
|
||||
+---------------------------+------------+----------------------+----------+------------------------------------------+
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
For more details, see [here][misconf].
|
||||
|
||||
[vulnerability]: ../docs/vulnerability/scanning/index.md
|
||||
[misconf]: ../docs/misconfiguration/index.md
|
||||
[secret]: ../docs/secret/scanning.md
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
"elements": [
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 476,
|
||||
"versionNonce": 916788210,
|
||||
"version": 787,
|
||||
"versionNonce": 1318065410,
|
||||
"isDeleted": false,
|
||||
"id": "BkXuq_6BxgqZGZWc8oCtu",
|
||||
"fillStyle": "hachure",
|
||||
@@ -15,21 +15,24 @@
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 599.211669921875,
|
||||
"y": 376.32061767578125,
|
||||
"x": 599.653076171875,
|
||||
"y": 734.7542114257812,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#fd7e14",
|
||||
"width": 1076.4584350585938,
|
||||
"width": 1227.452155219184,
|
||||
"height": 151.39703369140625,
|
||||
"seed": 1632394695,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": []
|
||||
"boundElements": [],
|
||||
"updated": 1650567161407,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 423,
|
||||
"versionNonce": 931200686,
|
||||
"version": 653,
|
||||
"versionNonce": 1863936606,
|
||||
"isDeleted": false,
|
||||
"id": "YQURTHNPSe05RPSlYRcok",
|
||||
"fillStyle": "hachure",
|
||||
@@ -38,8 +41,8 @@
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1119.1937866210938,
|
||||
"y": 403.56756591796875,
|
||||
"x": 1137.7821926540798,
|
||||
"y": 764.0207858615452,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 88,
|
||||
@@ -47,18 +50,23 @@
|
||||
"seed": 891391049,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": [],
|
||||
"boundElements": [],
|
||||
"updated": 1650567336690,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 36,
|
||||
"fontFamily": 1,
|
||||
"text": "Trivy",
|
||||
"baseline": 32,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top"
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "Trivy"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 758,
|
||||
"versionNonce": 813811122,
|
||||
"version": 1139,
|
||||
"versionNonce": 1994750018,
|
||||
"isDeleted": false,
|
||||
"id": "6dpF2EyZBtYgO6MrvGj0-",
|
||||
"fillStyle": "hachure",
|
||||
@@ -67,27 +75,32 @@
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 922.1328735351562,
|
||||
"y": 470.18975830078125,
|
||||
"x": 900.5941772460938,
|
||||
"y": 819.7337171766493,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 507,
|
||||
"height": 35,
|
||||
"width": 612,
|
||||
"height": 36,
|
||||
"seed": 687997545,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": [],
|
||||
"boundElements": [],
|
||||
"updated": 1650567334181,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 28,
|
||||
"fontFamily": 1,
|
||||
"text": "Vulnerability/Misconfiguration Scanner",
|
||||
"text": "Vulnerability/Misconfiguration/Secret Scanner",
|
||||
"baseline": 25,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top"
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "Vulnerability/Misconfiguration/Secret Scanner"
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 595,
|
||||
"versionNonce": 1705780846,
|
||||
"version": 805,
|
||||
"versionNonce": 1609410334,
|
||||
"isDeleted": false,
|
||||
"id": "cpnTMy7L2AUg9IDJppF4H",
|
||||
"fillStyle": "hachure",
|
||||
@@ -96,21 +109,24 @@
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 597.4796142578125,
|
||||
"y": 258.9286651611328,
|
||||
"x": 600.9835205078125,
|
||||
"y": 627.2060089111328,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#fab005",
|
||||
"width": 349.1224975585937,
|
||||
"height": 103.28388977050778,
|
||||
"width": 298.6342163085937,
|
||||
"height": 96.74092102050778,
|
||||
"seed": 77164935,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": []
|
||||
"boundElements": [],
|
||||
"updated": 1650567155021,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 921,
|
||||
"versionNonce": 929185650,
|
||||
"version": 1011,
|
||||
"versionNonce": 477782466,
|
||||
"isDeleted": false,
|
||||
"id": "9-blmNVtLesthMSY_f60t",
|
||||
"fillStyle": "hachure",
|
||||
@@ -119,8 +135,8 @@
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 655.6057739257812,
|
||||
"y": 292.4844055175781,
|
||||
"x": 628.7854614257812,
|
||||
"y": 658.9062805175781,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 238,
|
||||
@@ -128,18 +144,23 @@
|
||||
"seed": 860091815,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": [],
|
||||
"boundElements": [],
|
||||
"updated": 1650567155021,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 28,
|
||||
"fontFamily": 1,
|
||||
"text": "Container Images",
|
||||
"baseline": 25,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top"
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "Container Images"
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 853,
|
||||
"versionNonce": 377039022,
|
||||
"version": 1051,
|
||||
"versionNonce": 1210520414,
|
||||
"isDeleted": false,
|
||||
"id": "gugZxhi7ThlcjWY_MFO7q",
|
||||
"fillStyle": "hachure",
|
||||
@@ -148,21 +169,24 @@
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 955.929443359375,
|
||||
"y": 262.11351776123047,
|
||||
"x": 911.257568359375,
|
||||
"y": 625.7697677612305,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#be4bdb",
|
||||
"width": 359.85211181640625,
|
||||
"width": 452.44976806640636,
|
||||
"height": 99.05134582519533,
|
||||
"seed": 1232790121,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": []
|
||||
"boundElements": [],
|
||||
"updated": 1650567155021,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 1065,
|
||||
"versionNonce": 126714162,
|
||||
"version": 1202,
|
||||
"versionNonce": 842114,
|
||||
"isDeleted": false,
|
||||
"id": "K48gtpesBxIGJxLTnI2CB",
|
||||
"fillStyle": "hachure",
|
||||
@@ -171,8 +195,8 @@
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1064.449462890625,
|
||||
"y": 296.9230194091797,
|
||||
"x": 1065.672119140625,
|
||||
"y": 656.4816131591797,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 137,
|
||||
@@ -180,18 +204,23 @@
|
||||
"seed": 449264361,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": [],
|
||||
"boundElements": [],
|
||||
"updated": 1650567155021,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 28,
|
||||
"fontFamily": 1,
|
||||
"text": "Filesystem",
|
||||
"baseline": 25,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top"
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "Filesystem"
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 896,
|
||||
"versionNonce": 585884398,
|
||||
"version": 1163,
|
||||
"versionNonce": 1149481794,
|
||||
"isDeleted": false,
|
||||
"id": "La6f87LDZ0uEIZB947bXo",
|
||||
"fillStyle": "hachure",
|
||||
@@ -200,21 +229,24 @@
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1329.0839233398438,
|
||||
"y": 264.9097213745117,
|
||||
"x": 1375.0136108398438,
|
||||
"y": 626.2495651245117,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#12b886",
|
||||
"width": 346.5517578125,
|
||||
"width": 452.76554361979186,
|
||||
"height": 96.3990020751953,
|
||||
"seed": 2005637801,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": []
|
||||
"boundElements": [],
|
||||
"updated": 1650567157857,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 1186,
|
||||
"versionNonce": 1013615346,
|
||||
"version": 1371,
|
||||
"versionNonce": 1552918366,
|
||||
"isDeleted": false,
|
||||
"id": "aOgRPVQ81jhOfkvzjWTMF",
|
||||
"fillStyle": "hachure",
|
||||
@@ -223,286 +255,512 @@
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1392.300048828125,
|
||||
"y": 294.1288604736328,
|
||||
"x": 1490.9330512152778,
|
||||
"y": 654.3717736138237,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "transparent",
|
||||
"width": 223,
|
||||
"height": 35,
|
||||
"width": 224,
|
||||
"height": 36,
|
||||
"seed": 1284472935,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": [],
|
||||
"boundElements": [],
|
||||
"updated": 1650567166643,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 28,
|
||||
"fontFamily": 1,
|
||||
"text": "Git Repositories",
|
||||
"baseline": 25,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "top"
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "Git Repositories"
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 974,
|
||||
"versionNonce": 1011959534,
|
||||
"version": 2340,
|
||||
"versionNonce": 1952732126,
|
||||
"isDeleted": false,
|
||||
"id": "BYJwfkhd1BilbLQGc973f",
|
||||
"id": "p8fn5gPx8DfP8QE1lN98-",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1141.5093994140625,
|
||||
"y": 171.09759140014648,
|
||||
"x": 1064.5642678676506,
|
||||
"y": 537.71609717149,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#82c91e",
|
||||
"width": 169.93957519531259,
|
||||
"width": 144.5880126953128,
|
||||
"height": 77.80606079101562,
|
||||
"seed": 1923498546,
|
||||
"seed": 684019996,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": []
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 403,
|
||||
"versionNonce": 1635608306,
|
||||
"isDeleted": false,
|
||||
"id": "eedUyCpr8i1aY_3PHsHAB",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1149.8379821777344,
|
||||
"y": 197.31159591674805,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#82c91e",
|
||||
"width": 155,
|
||||
"height": 25,
|
||||
"seed": 595309038,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": [],
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"text": "Misconfiguration",
|
||||
"baseline": 18,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
"boundElements": [],
|
||||
"updated": 1650567155021,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 1035,
|
||||
"versionNonce": 1646453614,
|
||||
"version": 2497,
|
||||
"versionNonce": 832692482,
|
||||
"isDeleted": false,
|
||||
"id": "SPkrBrH6DGvkgQXtZQjIJ",
|
||||
"id": "kFTL0HnUdDs_ngg2xVFbn",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 959.9851989746094,
|
||||
"y": 170.4835319519043,
|
||||
"x": 600.7947366176511,
|
||||
"y": 538.67312842149,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#fa5252",
|
||||
"width": 169.93957519531259,
|
||||
"width": 144.5880126953128,
|
||||
"height": 77.80606079101562,
|
||||
"seed": 1896460914,
|
||||
"seed": 541443108,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": []
|
||||
"boundElements": [],
|
||||
"updated": 1650567155021,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 532,
|
||||
"versionNonce": 1887556210,
|
||||
"type": "rectangle",
|
||||
"version": 2345,
|
||||
"versionNonce": 1129796482,
|
||||
"isDeleted": false,
|
||||
"id": "n06MNIqirDmVZBkDg_UPV",
|
||||
"id": "KIztJcYHiVtM-GMlZAXAE",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 988.8137817382812,
|
||||
"y": 196.69753646850586,
|
||||
"x": 753.0955178676511,
|
||||
"y": 538.61062842149,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#4c6ef5",
|
||||
"width": 145,
|
||||
"height": 77.80606079101562,
|
||||
"seed": 424425892,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1650567234562,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 2329,
|
||||
"versionNonce": 1518497986,
|
||||
"isDeleted": false,
|
||||
"id": "IWq_LcOearBV5cvqbJ_-o",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 911.3064553676511,
|
||||
"y": 537.97781592149,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#fa5252",
|
||||
"width": 144.5880126953128,
|
||||
"height": 77.80606079101562,
|
||||
"seed": 468230812,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1650567155021,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 2318,
|
||||
"versionNonce": 1504408670,
|
||||
"isDeleted": false,
|
||||
"id": "TXeK066NA0hvyPSeZl1a5",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1218.075986617651,
|
||||
"y": 536.86062842149,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#4c6ef5",
|
||||
"width": 144.5880126953128,
|
||||
"height": 77.80606079101562,
|
||||
"seed": 1408574372,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1650567155021,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 2364,
|
||||
"versionNonce": 3868802,
|
||||
"isDeleted": false,
|
||||
"id": "10WjipxoLx2zzSI91pXbR",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1375.544736617651,
|
||||
"y": 537.04812842149,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#fa5252",
|
||||
"width": 144.5880126953128,
|
||||
"height": 77.80606079101562,
|
||||
"seed": 1813731484,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1650567155021,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 2355,
|
||||
"versionNonce": 2067347614,
|
||||
"isDeleted": false,
|
||||
"id": "M7Cngti6H0_kawKRN8yJ6",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1528.868955367651,
|
||||
"y": 535.89187842149,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#82c91e",
|
||||
"width": 144.5880126953128,
|
||||
"height": 77.80606079101562,
|
||||
"seed": 1260603804,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1650567155021,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 597,
|
||||
"versionNonce": 1519036482,
|
||||
"isDeleted": false,
|
||||
"id": "GHDrLyBOErQtv_WT5Lx3p",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 614.2293679653073,
|
||||
"y": 565.5605338169978,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#82c91e",
|
||||
"width": 114,
|
||||
"height": 25,
|
||||
"seed": 1131832750,
|
||||
"seed": 13297180,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": [],
|
||||
"boundElements": [],
|
||||
"updated": 1650567155021,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"text": "Vulnerability",
|
||||
"baseline": 18,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 1072,
|
||||
"versionNonce": 789595566,
|
||||
"isDeleted": false,
|
||||
"id": "0JP6OL7EFfoH4E4vFARFl",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1508.9087371826172,
|
||||
"y": 170.7038917541504,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#82c91e",
|
||||
"width": 169.93957519531259,
|
||||
"height": 77.80606079101562,
|
||||
"seed": 101784622,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": []
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "Vulnerability"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 496,
|
||||
"versionNonce": 1027781682,
|
||||
"version": 655,
|
||||
"versionNonce": 1728345310,
|
||||
"isDeleted": false,
|
||||
"id": "jRmlh5MZuRKm3FtbC6qdZ",
|
||||
"id": "Iq57wFRtO1a8AU0rT6lRD",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1517.237319946289,
|
||||
"y": 196.91789627075195,
|
||||
"x": 1533.6317117153076,
|
||||
"y": 565.2714713169978,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#82c91e",
|
||||
"width": 155,
|
||||
"height": 25,
|
||||
"seed": 1950385586,
|
||||
"width": 136.25488281249991,
|
||||
"height": 21.80078124999998,
|
||||
"seed": 1329695396,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": [],
|
||||
"fontSize": 20,
|
||||
"boundElements": [],
|
||||
"updated": 1650567155021,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 17.440624999999976,
|
||||
"fontFamily": 1,
|
||||
"text": "Misconfiguration",
|
||||
"baseline": 18,
|
||||
"baseline": 14.800781249999979,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 1133,
|
||||
"versionNonce": 882335726,
|
||||
"isDeleted": false,
|
||||
"id": "EQRF92xU4o9CfeHHvbd-a",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1327.384536743164,
|
||||
"y": 170.0898323059082,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#fa5252",
|
||||
"width": 169.93957519531259,
|
||||
"height": 77.80606079101562,
|
||||
"seed": 1379493486,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": []
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "Misconfiguration"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 569,
|
||||
"versionNonce": 184638962,
|
||||
"version": 666,
|
||||
"versionNonce": 1364217858,
|
||||
"isDeleted": false,
|
||||
"id": "_04YR8geM-ar9vZhNZtSj",
|
||||
"id": "gjnZl9nxrqzliwPk8sbK-",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1356.213119506836,
|
||||
"y": 196.30383682250977,
|
||||
"x": 1067.4339578090576,
|
||||
"y": 565.8827994419978,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#82c91e",
|
||||
"width": 136.25488281249991,
|
||||
"height": 21.80078124999998,
|
||||
"seed": 290336932,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1650567155021,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 17.440624999999976,
|
||||
"fontFamily": 1,
|
||||
"text": "Misconfiguration",
|
||||
"baseline": 14.800781249999979,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "Misconfiguration"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 613,
|
||||
"versionNonce": 1721641246,
|
||||
"isDeleted": false,
|
||||
"id": "_cm6xpfcL9Yv2XBK5MBZF",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1390.1199929653073,
|
||||
"y": 563.8456900669978,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#82c91e",
|
||||
"width": 114,
|
||||
"height": 25,
|
||||
"seed": 357105522,
|
||||
"seed": 807441828,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": [],
|
||||
"boundElements": [],
|
||||
"updated": 1650567155021,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"text": "Vulnerability",
|
||||
"baseline": 18,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 1215,
|
||||
"versionNonce": 650195502,
|
||||
"isDeleted": false,
|
||||
"id": "8SHSNGf7PNddFLi2ZA3Vi",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 599.9767150878906,
|
||||
"y": 169.0025749206543,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#fa5252",
|
||||
"width": 344.1738281250001,
|
||||
"height": 77.80606079101562,
|
||||
"seed": 1986948530,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": []
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "Vulnerability"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 680,
|
||||
"versionNonce": 113561522,
|
||||
"version": 635,
|
||||
"versionNonce": 2022375362,
|
||||
"isDeleted": false,
|
||||
"id": "3Z5w3RXdgpvP43dlHqq26",
|
||||
"id": "An4-igVUkLzCwSdvDmtZl",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 700.3721618652344,
|
||||
"y": 190.79421615600586,
|
||||
"x": 925.6707742153073,
|
||||
"y": 560.4550650669978,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#82c91e",
|
||||
"width": 160,
|
||||
"height": 35,
|
||||
"seed": 1077804654,
|
||||
"width": 114,
|
||||
"height": 25,
|
||||
"seed": 1262859164,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElementIds": [],
|
||||
"fontSize": 28,
|
||||
"boundElements": [],
|
||||
"updated": 1650567155021,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"text": "Vulnerability",
|
||||
"baseline": 25,
|
||||
"baseline": 18,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle"
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "Vulnerability"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 681,
|
||||
"versionNonce": 1813371650,
|
||||
"isDeleted": false,
|
||||
"id": "p3-AVxdx5KP6eNViMVTLq",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 792.0184304653073,
|
||||
"y": 564.4160025669978,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#82c91e",
|
||||
"width": 65,
|
||||
"height": 25,
|
||||
"seed": 729823772,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1650567236437,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"text": "Secret",
|
||||
"baseline": 18,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "Secret"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 692,
|
||||
"versionNonce": 621238878,
|
||||
"isDeleted": false,
|
||||
"id": "7fe9NOM7QTEEW7nyXAMjh",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1259.0613992153073,
|
||||
"y": 562.4238150669978,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#82c91e",
|
||||
"width": 65,
|
||||
"height": 25,
|
||||
"seed": 968541220,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1650567238433,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"text": "Secret",
|
||||
"baseline": 18,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "Secret"
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 2417,
|
||||
"versionNonce": 1222703518,
|
||||
"isDeleted": false,
|
||||
"id": "Fq7meULupm1A9leboPlko",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1684.2588079543348,
|
||||
"y": 536.1830067815082,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#4c6ef5",
|
||||
"width": 144.5880126953128,
|
||||
"height": 77.80606079101562,
|
||||
"seed": 230693534,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1650567155021,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 760,
|
||||
"versionNonce": 503898690,
|
||||
"isDeleted": false,
|
||||
"id": "OUGk8nZzvgcKUHhKUcQov",
|
||||
"fillStyle": "hachure",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 1723.2672240242127,
|
||||
"y": 561.8650424396028,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#82c91e",
|
||||
"width": 65,
|
||||
"height": 25,
|
||||
"seed": 2044527454,
|
||||
"groupIds": [],
|
||||
"strokeSharpness": "sharp",
|
||||
"boundElements": [],
|
||||
"updated": 1650567240607,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 20,
|
||||
"fontFamily": 1,
|
||||
"text": "Secret",
|
||||
"baseline": 18,
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "Secret"
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"gridSize": null,
|
||||
"viewBackgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
BIN
docs/imgs/excalidraw/overview.png
Normal file
BIN
docs/imgs/excalidraw/overview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 878 KiB After Width: | Height: | Size: 2.1 MiB |
@@ -6,10 +6,10 @@ hide:
|
||||
|
||||
{ align=left }
|
||||
|
||||
`Trivy` (`tri` pronounced like **tri**gger, `vy` pronounced like en**vy**) is a simple and comprehensive [vulnerability][vulnerability]/[misconfiguration][misconf] scanner for containers and other artifacts.
|
||||
A software vulnerability is a glitch, flaw, or weakness present in the software or in an Operating System.
|
||||
`Trivy` (`tri` pronounced like **tri**gger, `vy` pronounced like en**vy**) is a simple and comprehensive [vulnerability][vulnerability]/[misconfiguration][misconf]/[secret][secret] scanner for containers and other artifacts.
|
||||
`Trivy` detects vulnerabilities of [OS packages][os] (Alpine, RHEL, CentOS, etc.) and [language-specific packages][lang] (Bundler, Composer, npm, yarn, etc.).
|
||||
In addition, `Trivy` scans [Infrastructure as Code (IaC) files][iac] such as Terraform and Kubernetes, to detect potential configuration issues that expose your deployments to the risk of attack.
|
||||
`Trivy` also scans [hardcoded secrets][secret] like passwords, api keys, and tokens.
|
||||
`Trivy` is easy to use. Just install the binary and you're ready to scan.
|
||||
All you need to do for scanning is to specify a target such as an image name of the container.
|
||||
|
||||
@@ -40,6 +40,7 @@ Contact us about any matter by opening a GitHub Discussion [here][discussions]
|
||||
|
||||
[vulnerability]: docs/vulnerability/scanning/index.md
|
||||
[misconf]: docs/misconfiguration/index.md
|
||||
[secret]: docs/secret/scanning.md
|
||||
[os]: docs/vulnerability/detection/os.md
|
||||
[lang]: docs/vulnerability/detection/language.md
|
||||
[iac]: docs/misconfiguration/iac.md
|
||||
|
||||
2
go.mod
2
go.mod
@@ -7,7 +7,7 @@ require (
|
||||
github.com/Masterminds/sprig/v3 v3.2.2
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46
|
||||
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986
|
||||
github.com/aquasecurity/fanal v0.0.0-20220421095103-63f3f8193fa8
|
||||
github.com/aquasecurity/fanal v0.0.0-20220421211205-bf8b076b6bf1
|
||||
github.com/aquasecurity/go-dep-parser v0.0.0-20220412145205-d0501f906d90
|
||||
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce
|
||||
github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798
|
||||
|
||||
4
go.sum
4
go.sum
@@ -237,8 +237,8 @@ github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 h1:2a30
|
||||
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986/go.mod h1:NT+jyeCzXk6vXR5MTkdn4z64TgGfE5HMLC8qfj5unl8=
|
||||
github.com/aquasecurity/defsec v0.28.5-0.20220416075528-0f0c8fdf63b8 h1:TQOc6oTNT1943KbsivdxBnNHlZbp6cF3AcMzVLJCotg=
|
||||
github.com/aquasecurity/defsec v0.28.5-0.20220416075528-0f0c8fdf63b8/go.mod h1:vUdThwusBM7y1gJ7CVX3+h3bsPvpmOIEp3NEdzTDkhA=
|
||||
github.com/aquasecurity/fanal v0.0.0-20220421095103-63f3f8193fa8 h1:3kXo5widBEz1euPnd292X5JSq0kEDuobi3YAXl4DbhU=
|
||||
github.com/aquasecurity/fanal v0.0.0-20220421095103-63f3f8193fa8/go.mod h1:PYU7igSuHlhOFTVNhMlv/P9oTYbcgMb0wn5+Sz+xkMs=
|
||||
github.com/aquasecurity/fanal v0.0.0-20220421211205-bf8b076b6bf1 h1:57WZwnbs6hC38uhHu88T6WYqEsbmgvhsA+r81a5P13Q=
|
||||
github.com/aquasecurity/fanal v0.0.0-20220421211205-bf8b076b6bf1/go.mod h1:zDhHI2dChX5My7gGtY6PGgKTfhJ6y8c6cDI+y38rVk8=
|
||||
github.com/aquasecurity/go-dep-parser v0.0.0-20220412145205-d0501f906d90 h1:uZcI5qV7J1pzOc6W49l7iEey/KtEVlaqsNU5l65vZLk=
|
||||
github.com/aquasecurity/go-dep-parser v0.0.0-20220412145205-d0501f906d90/go.mod h1:rK/5BoRt8/D7xXydoVVeBaQuk6zDJ6W+FWz/RqFuJxI=
|
||||
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce h1:QgBRgJvtEOBtUXilDb1MLi1p1MWoyFDXAu5DEUl5nwM=
|
||||
|
||||
@@ -24,6 +24,7 @@ func TestFilesystem(t *testing.T) {
|
||||
namespaces []string
|
||||
listAllPkgs bool
|
||||
input string
|
||||
secretConfig string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -100,6 +101,15 @@ func TestFilesystem(t *testing.T) {
|
||||
},
|
||||
golden: "testdata/dockerfile-custom-policies.json.golden",
|
||||
},
|
||||
{
|
||||
name: "secrets",
|
||||
args: args{
|
||||
securityChecks: "vuln,secret",
|
||||
input: "testdata/fixtures/fs/secrets",
|
||||
secretConfig: "testdata/fixtures/fs/secrets/trivy-secret.yaml",
|
||||
},
|
||||
golden: "testdata/secrets.json.golden",
|
||||
},
|
||||
}
|
||||
|
||||
// Set up testing DB
|
||||
@@ -143,6 +153,10 @@ func TestFilesystem(t *testing.T) {
|
||||
osArgs = append(osArgs, "--list-all-pkgs")
|
||||
}
|
||||
|
||||
if tt.args.secretConfig != "" {
|
||||
osArgs = append(osArgs, "--secret-config", tt.args.secretConfig)
|
||||
}
|
||||
|
||||
osArgs = append(osArgs, "--output", outputFile)
|
||||
osArgs = append(osArgs, tt.args.input)
|
||||
|
||||
|
||||
7
integration/testdata/fixtures/fs/secrets/deploy.sh
vendored
Normal file
7
integration/testdata/fixtures/fs/secrets/deploy.sh
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
export AWS_ACCESS_KEY_ID=AKIAABCDEFGHI1234567
|
||||
|
||||
export GITHUB_PAT=ghp_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
|
||||
echo mysecret
|
||||
8
integration/testdata/fixtures/fs/secrets/trivy-secret.yaml
vendored
Normal file
8
integration/testdata/fixtures/fs/secrets/trivy-secret.yaml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
rules:
|
||||
- id: mysecret
|
||||
category: Custom
|
||||
title: My Secret
|
||||
severity: HIGH
|
||||
regex: mysecret
|
||||
disable-rules:
|
||||
- github-pat
|
||||
31
integration/testdata/secrets.json.golden
vendored
Normal file
31
integration/testdata/secrets.json.golden
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"SchemaVersion": 2,
|
||||
"ArtifactName": "testdata/fixtures/fs/secrets",
|
||||
"ArtifactType": "filesystem",
|
||||
"Results": [
|
||||
{
|
||||
"Target": "deploy.sh",
|
||||
"Class": "secret",
|
||||
"Secrets": [
|
||||
{
|
||||
"RuleID": "aws-access-key-id",
|
||||
"Category": "AWS",
|
||||
"Severity": "CRITICAL",
|
||||
"Title": "AWS Access Key ID",
|
||||
"StartLine": 3,
|
||||
"EndLine": 3,
|
||||
"Match": "export AWS_ACCESS_KEY_ID=*****"
|
||||
},
|
||||
{
|
||||
"RuleID": "mysecret",
|
||||
"Category": "Custom",
|
||||
"Severity": "HIGH",
|
||||
"Title": "My Secret",
|
||||
"StartLine": 7,
|
||||
"EndLine": 7,
|
||||
"Match": "echo *****"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -60,6 +60,9 @@ nav:
|
||||
- vs Conftest: docs/misconfiguration/comparison/conftest.md
|
||||
- vs tfsec: docs/misconfiguration/comparison/tfsec.md
|
||||
- vs cfsec: docs/misconfiguration/comparison/cfsec.md
|
||||
- Secret:
|
||||
- Scanning: docs/secret/scanning.md
|
||||
- Configuration: docs/secret/configuration.md
|
||||
- SBOM:
|
||||
- Overview: docs/sbom/index.md
|
||||
- CycloneDX: docs/sbom/cyclonedx.md
|
||||
|
||||
@@ -147,8 +147,8 @@ var (
|
||||
|
||||
securityChecksFlag = cli.StringFlag{
|
||||
Name: "security-checks",
|
||||
Value: types.SecurityCheckVulnerability,
|
||||
Usage: "comma-separated list of what security issues to detect (vuln,config)",
|
||||
Value: fmt.Sprintf("%s,%s", types.SecurityCheckVulnerability, types.SecurityCheckSecret),
|
||||
Usage: "comma-separated list of what security issues to detect (vuln,config,secret)",
|
||||
EnvVars: []string{"TRIVY_SECURITY_CHECKS"},
|
||||
}
|
||||
|
||||
@@ -332,6 +332,13 @@ var (
|
||||
EnvVars: []string{"TRIVY_DB_REPOSITORY"},
|
||||
}
|
||||
|
||||
secretConfig = cli.StringFlag{
|
||||
Name: "secret-config",
|
||||
Usage: "specify a path to config file for secret scanning",
|
||||
Value: "trivy-secret.yaml",
|
||||
EnvVars: []string{"TRIVY_SECRET_CONFIG"},
|
||||
}
|
||||
|
||||
// Global flags
|
||||
globalFlags = []cli.Flag{
|
||||
&quietFlag,
|
||||
@@ -456,6 +463,7 @@ func NewImageCommand() *cli.Command {
|
||||
&offlineScan,
|
||||
&insecureFlag,
|
||||
&dbRepositoryFlag,
|
||||
&secretConfig,
|
||||
stringSliceFlag(skipFiles),
|
||||
stringSliceFlag(skipDirs),
|
||||
},
|
||||
@@ -493,6 +501,7 @@ func NewFilesystemCommand() *cli.Command {
|
||||
&listAllPackages,
|
||||
&offlineScan,
|
||||
&dbRepositoryFlag,
|
||||
&secretConfig,
|
||||
stringSliceFlag(skipFiles),
|
||||
stringSliceFlag(skipDirs),
|
||||
|
||||
@@ -540,6 +549,7 @@ func NewRootfsCommand() *cli.Command {
|
||||
&listAllPackages,
|
||||
&offlineScan,
|
||||
&dbRepositoryFlag,
|
||||
&secretConfig,
|
||||
stringSliceFlag(skipFiles),
|
||||
stringSliceFlag(skipDirs),
|
||||
stringSliceFlag(configPolicy),
|
||||
@@ -584,6 +594,7 @@ func NewRepositoryCommand() *cli.Command {
|
||||
&offlineScan,
|
||||
&insecureFlag,
|
||||
&dbRepositoryFlag,
|
||||
&secretConfig,
|
||||
stringSliceFlag(skipFiles),
|
||||
stringSliceFlag(skipDirs),
|
||||
},
|
||||
@@ -620,6 +631,7 @@ func NewClientCommand() *cli.Command {
|
||||
&listAllPackages,
|
||||
&offlineScan,
|
||||
&insecureFlag,
|
||||
&secretConfig,
|
||||
|
||||
&token,
|
||||
&tokenHeader,
|
||||
|
||||
@@ -12,8 +12,7 @@ import (
|
||||
|
||||
// filesystemStandaloneScanner initializes a filesystem scanner in standalone mode
|
||||
func filesystemStandaloneScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
|
||||
s, cleanup, err := initializeFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache,
|
||||
conf.ArtifactOption, conf.MisconfOption)
|
||||
s, cleanup, err := initializeFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
|
||||
}
|
||||
@@ -22,8 +21,7 @@ func filesystemStandaloneScanner(ctx context.Context, conf scannerConfig) (scann
|
||||
|
||||
// filesystemRemoteScanner initializes a filesystem scanner in client/server mode
|
||||
func filesystemRemoteScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
|
||||
s, cleanup, err := initializeRemoteFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption,
|
||||
conf.ArtifactOption, conf.MisconfOption)
|
||||
s, cleanup, err := initializeRemoteFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption, conf.ArtifactOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
|
||||
}
|
||||
@@ -39,6 +37,7 @@ func FilesystemRun(ctx *cli.Context) error {
|
||||
|
||||
// Disable the individual package scanning
|
||||
opt.DisabledAnalyzers = analyzer.TypeIndividualPkgs
|
||||
//opt.DisabledAnalyzers = append(opt.DisabledAnalyzers, analyzer.TypeSecret)
|
||||
|
||||
// client/server mode
|
||||
if opt.RemoteAddr != "" {
|
||||
|
||||
@@ -19,7 +19,7 @@ func imageScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, fun
|
||||
return scanner.Scanner{}, nil, err
|
||||
}
|
||||
s, cleanup, err := initializeDockerScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache,
|
||||
dockerOpt, conf.ArtifactOption, conf.MisconfOption)
|
||||
dockerOpt, conf.ArtifactOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a docker scanner: %w", err)
|
||||
}
|
||||
@@ -29,8 +29,7 @@ func imageScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, fun
|
||||
// archiveScanner initializes an image archive scanner in standalone mode
|
||||
// $ trivy image --input alpine.tar
|
||||
func archiveScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
|
||||
s, err := initializeArchiveScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache,
|
||||
conf.ArtifactOption, conf.MisconfOption)
|
||||
s, err := initializeArchiveScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize the archive scanner: %w", err)
|
||||
}
|
||||
@@ -48,7 +47,7 @@ func remoteImageScanner(ctx context.Context, conf scannerConfig) (
|
||||
}
|
||||
|
||||
s, cleanup, err := initializeRemoteDockerScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption,
|
||||
dockerOpt, conf.ArtifactOption, conf.MisconfOption)
|
||||
dockerOpt, conf.ArtifactOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize the docker scanner: %w", err)
|
||||
}
|
||||
@@ -59,8 +58,7 @@ func remoteImageScanner(ctx context.Context, conf scannerConfig) (
|
||||
// $ trivy image --server localhost:4954 --input alpine.tar
|
||||
func remoteArchiveScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
|
||||
// Scan tar file
|
||||
s, err := initializeRemoteArchiveScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption,
|
||||
conf.ArtifactOption, conf.MisconfOption)
|
||||
s, err := initializeRemoteArchiveScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption, conf.ArtifactOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize the archive scanner: %w", err)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer/config"
|
||||
"github.com/aquasecurity/fanal/artifact"
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
"github.com/aquasecurity/fanal/types"
|
||||
@@ -24,8 +23,8 @@ import (
|
||||
// initializeDockerScanner is for container image scanning in standalone mode
|
||||
// e.g. dockerd, container registry, podman, etc.
|
||||
func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache,
|
||||
localArtifactCache cache.LocalArtifactCache, dockerOpt types.DockerOption, artifactOption artifact.Option,
|
||||
configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
localArtifactCache cache.LocalArtifactCache, dockerOpt types.DockerOption, artifactOption artifact.Option) (
|
||||
scanner.Scanner, func(), error) {
|
||||
wire.Build(scanner.StandaloneDockerSet)
|
||||
return scanner.Scanner{}, nil, nil
|
||||
}
|
||||
@@ -33,23 +32,20 @@ func initializeDockerScanner(ctx context.Context, imageName string, artifactCach
|
||||
// initializeArchiveScanner is for container image archive scanning in standalone mode
|
||||
// e.g. docker save -o alpine.tar alpine:3.15
|
||||
func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache,
|
||||
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option,
|
||||
configScannerOption config.ScannerOption) (scanner.Scanner, error) {
|
||||
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, error) {
|
||||
wire.Build(scanner.StandaloneArchiveSet)
|
||||
return scanner.Scanner{}, nil
|
||||
}
|
||||
|
||||
// initializeFilesystemScanner is for filesystem scanning in standalone mode
|
||||
func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache,
|
||||
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option,
|
||||
configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
|
||||
wire.Build(scanner.StandaloneFilesystemSet)
|
||||
return scanner.Scanner{}, nil, nil
|
||||
}
|
||||
|
||||
func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache,
|
||||
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option,
|
||||
configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
|
||||
wire.Build(scanner.StandaloneRepositorySet)
|
||||
return scanner.Scanner{}, nil, nil
|
||||
}
|
||||
@@ -66,8 +62,7 @@ func initializeResultClient() result.Client {
|
||||
// initializeRemoteDockerScanner is for container image scanning in client/server mode
|
||||
// e.g. dockerd, container registry, podman, etc.
|
||||
func initializeRemoteDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache,
|
||||
remoteScanOptions client.ScannerOption, dockerOpt types.DockerOption, artifactOption artifact.Option,
|
||||
configScannerOption config.ScannerOption) (
|
||||
remoteScanOptions client.ScannerOption, dockerOpt types.DockerOption, artifactOption artifact.Option) (
|
||||
scanner.Scanner, func(), error) {
|
||||
wire.Build(scanner.RemoteDockerSet)
|
||||
return scanner.Scanner{}, nil, nil
|
||||
@@ -76,16 +71,14 @@ func initializeRemoteDockerScanner(ctx context.Context, imageName string, artifa
|
||||
// initializeRemoteArchiveScanner is for container image archive scanning in client/server mode
|
||||
// e.g. docker save -o alpine.tar alpine:3.15
|
||||
func initializeRemoteArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache,
|
||||
remoteScanOptions client.ScannerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (
|
||||
scanner.Scanner, error) {
|
||||
remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, error) {
|
||||
wire.Build(scanner.RemoteArchiveSet)
|
||||
return scanner.Scanner{}, nil
|
||||
}
|
||||
|
||||
// initializeRemoteFilesystemScanner is for filesystem scanning in client/server mode
|
||||
func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache,
|
||||
remoteScanOptions client.ScannerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (
|
||||
scanner.Scanner, func(), error) {
|
||||
remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
|
||||
wire.Build(scanner.RemoteFilesystemSet)
|
||||
return scanner.Scanner{}, nil, nil
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ type Option struct {
|
||||
option.ConfigOption
|
||||
option.RemoteOption
|
||||
option.SbomOption
|
||||
option.SecretOption
|
||||
|
||||
// We don't want to allow disabled analyzers to be passed by users,
|
||||
// but it differs depending on scanning modes.
|
||||
@@ -42,6 +43,7 @@ func NewOption(c *cli.Context) (Option, error) {
|
||||
ConfigOption: option.NewConfigOption(c),
|
||||
RemoteOption: option.NewRemoteOption(c),
|
||||
SbomOption: option.NewSbomOption(c),
|
||||
SecretOption: option.NewSecretOption(c),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,7 @@ import (
|
||||
|
||||
// filesystemStandaloneScanner initializes a repository scanner in standalone mode
|
||||
func repositoryScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
|
||||
s, cleanup, err := initializeRepositoryScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache,
|
||||
conf.ArtifactOption, conf.MisconfOption)
|
||||
s, cleanup, err := initializeRepositoryScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
"github.com/aquasecurity/fanal/analyzer/config"
|
||||
"github.com/aquasecurity/fanal/analyzer/secret"
|
||||
"github.com/aquasecurity/fanal/artifact"
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
@@ -41,9 +42,6 @@ type scannerConfig struct {
|
||||
|
||||
// Artifact options
|
||||
ArtifactOption artifact.Option
|
||||
|
||||
// Misconfiguration scanning options
|
||||
MisconfOption config.ScannerOption
|
||||
}
|
||||
|
||||
// InitializeScanner defines the initialize function signature of scanner
|
||||
@@ -189,11 +187,16 @@ func disabledAnalyzers(opt Option) []analyzer.Type {
|
||||
analyzers = append(analyzers, analyzer.TypeApkCommand)
|
||||
}
|
||||
|
||||
// Don't analyze programming language packages when not running in 'library' mode
|
||||
// Do not analyze programming language packages when not running in 'library' mode
|
||||
if !slices.Contains(opt.VulnType, types.VulnTypeLibrary) {
|
||||
analyzers = append(analyzers, analyzer.TypeLanguages...)
|
||||
}
|
||||
|
||||
// Do not perform secret scanning when it is not specified.
|
||||
if !slices.Contains(opt.SecurityChecks, types.SecurityCheckSecret) {
|
||||
analyzers = append(analyzers, analyzer.TypeSecret)
|
||||
}
|
||||
|
||||
return analyzers
|
||||
}
|
||||
|
||||
@@ -246,8 +249,15 @@ func scan(ctx context.Context, opt Option, initializeScanner InitializeScanner,
|
||||
InsecureSkipTLS: opt.Insecure,
|
||||
Offline: opt.OfflineScan,
|
||||
NoProgress: opt.NoProgress || opt.Quiet,
|
||||
|
||||
// For misconfiguration scanning
|
||||
MisconfScannerOption: configScannerOptions,
|
||||
|
||||
// For secret scanning
|
||||
SecretScannerOption: secret.ScannerOption{
|
||||
ConfigPath: opt.SecretConfigPath,
|
||||
},
|
||||
},
|
||||
MisconfOption: configScannerOptions,
|
||||
})
|
||||
if err != nil {
|
||||
return types.Report{}, xerrors.Errorf("unable to initialize a scanner: %w", err)
|
||||
@@ -269,7 +279,7 @@ func filter(ctx context.Context, opt Option, report types.Report) (types.Report,
|
||||
if opt.RemoteAddr == "" {
|
||||
resultClient.FillVulnerabilityInfo(results[i].Vulnerabilities, results[i].Type)
|
||||
}
|
||||
vulns, misconfSummary, misconfs, err := resultClient.Filter(ctx, results[i].Vulnerabilities, results[i].Misconfigurations,
|
||||
vulns, misconfSummary, misconfs, secrets, err := resultClient.Filter(ctx, results[i].Vulnerabilities, results[i].Misconfigurations, results[i].Secrets,
|
||||
opt.Severities, opt.IgnoreUnfixed, opt.IncludeNonFailures, opt.IgnoreFile, opt.IgnorePolicy)
|
||||
if err != nil {
|
||||
return types.Report{}, xerrors.Errorf("unable to filter vulnerabilities: %w", err)
|
||||
@@ -277,6 +287,7 @@ func filter(ctx context.Context, opt Option, report types.Report) (types.Report,
|
||||
results[i].Vulnerabilities = vulns
|
||||
results[i].Misconfigurations = misconfs
|
||||
results[i].MisconfSummary = misconfSummary
|
||||
results[i].Secrets = secrets
|
||||
}
|
||||
return report, nil
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ package artifact
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/aquasecurity/fanal/analyzer/config"
|
||||
"github.com/aquasecurity/fanal/applier"
|
||||
"github.com/aquasecurity/fanal/artifact"
|
||||
image2 "github.com/aquasecurity/fanal/artifact/image"
|
||||
@@ -29,7 +28,7 @@ import (
|
||||
|
||||
// initializeDockerScanner is for container image scanning in standalone mode
|
||||
// e.g. dockerd, container registry, podman, etc.
|
||||
func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, dockerOpt types.DockerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, dockerOpt types.DockerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
|
||||
applierApplier := applier.NewApplier(localArtifactCache)
|
||||
detector := ospkg.Detector{}
|
||||
localScanner := local.NewScanner(applierApplier, detector)
|
||||
@@ -37,7 +36,7 @@ func initializeDockerScanner(ctx context.Context, imageName string, artifactCach
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, err
|
||||
}
|
||||
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption, configScannerOption)
|
||||
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption)
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return scanner.Scanner{}, nil, err
|
||||
@@ -50,7 +49,7 @@ func initializeDockerScanner(ctx context.Context, imageName string, artifactCach
|
||||
|
||||
// initializeArchiveScanner is for container image archive scanning in standalone mode
|
||||
// e.g. docker save -o alpine.tar alpine:3.15
|
||||
func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, error) {
|
||||
func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, error) {
|
||||
applierApplier := applier.NewApplier(localArtifactCache)
|
||||
detector := ospkg.Detector{}
|
||||
localScanner := local.NewScanner(applierApplier, detector)
|
||||
@@ -58,7 +57,7 @@ func initializeArchiveScanner(ctx context.Context, filePath string, artifactCach
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, err
|
||||
}
|
||||
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption, configScannerOption)
|
||||
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, err
|
||||
}
|
||||
@@ -67,11 +66,11 @@ func initializeArchiveScanner(ctx context.Context, filePath string, artifactCach
|
||||
}
|
||||
|
||||
// initializeFilesystemScanner is for filesystem scanning in standalone mode
|
||||
func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
|
||||
applierApplier := applier.NewApplier(localArtifactCache)
|
||||
detector := ospkg.Detector{}
|
||||
localScanner := local.NewScanner(applierApplier, detector)
|
||||
artifactArtifact, err := local2.NewArtifact(path, artifactCache, artifactOption, configScannerOption)
|
||||
artifactArtifact, err := local2.NewArtifact(path, artifactCache, artifactOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, err
|
||||
}
|
||||
@@ -80,11 +79,11 @@ func initializeFilesystemScanner(ctx context.Context, path string, artifactCache
|
||||
}, nil
|
||||
}
|
||||
|
||||
func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
|
||||
applierApplier := applier.NewApplier(localArtifactCache)
|
||||
detector := ospkg.Detector{}
|
||||
localScanner := local.NewScanner(applierApplier, detector)
|
||||
artifactArtifact, cleanup, err := remote.NewArtifact(url, artifactCache, artifactOption, configScannerOption)
|
||||
artifactArtifact, cleanup, err := remote.NewArtifact(url, artifactCache, artifactOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, err
|
||||
}
|
||||
@@ -95,20 +94,21 @@ func initializeRepositoryScanner(ctx context.Context, url string, artifactCache
|
||||
}
|
||||
|
||||
func initializeResultClient() result.Client {
|
||||
dbConfig := db.Config{}
|
||||
client := result.NewClient(dbConfig)
|
||||
config := db.Config{}
|
||||
client := result.NewClient(config)
|
||||
return client
|
||||
}
|
||||
|
||||
// initializeRemoteDockerScanner is for container image scanning in client/server mode
|
||||
// e.g. dockerd, container registry, podman, etc.
|
||||
func initializeRemoteDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, dockerOpt types.DockerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
clientScanner := client.NewScanner(remoteScanOptions)
|
||||
func initializeRemoteDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, dockerOpt types.DockerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
|
||||
v := _wireValue
|
||||
clientScanner := client.NewScanner(remoteScanOptions, v...)
|
||||
typesImage, cleanup, err := image.NewDockerImage(ctx, imageName, dockerOpt)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, err
|
||||
}
|
||||
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption, configScannerOption)
|
||||
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption)
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return scanner.Scanner{}, nil, err
|
||||
@@ -119,15 +119,20 @@ func initializeRemoteDockerScanner(ctx context.Context, imageName string, artifa
|
||||
}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
_wireValue = []client.Option(nil)
|
||||
)
|
||||
|
||||
// initializeRemoteArchiveScanner is for container image archive scanning in client/server mode
|
||||
// e.g. docker save -o alpine.tar alpine:3.15
|
||||
func initializeRemoteArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, error) {
|
||||
clientScanner := client.NewScanner(remoteScanOptions)
|
||||
func initializeRemoteArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, error) {
|
||||
v := _wireValue
|
||||
clientScanner := client.NewScanner(remoteScanOptions, v...)
|
||||
typesImage, err := image.NewArchiveImage(filePath)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, err
|
||||
}
|
||||
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption, configScannerOption)
|
||||
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, err
|
||||
}
|
||||
@@ -136,9 +141,10 @@ func initializeRemoteArchiveScanner(ctx context.Context, filePath string, artifa
|
||||
}
|
||||
|
||||
// initializeRemoteFilesystemScanner is for filesystem scanning in client/server mode
|
||||
func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
clientScanner := client.NewScanner(remoteScanOptions)
|
||||
artifactArtifact, err := local2.NewArtifact(path, artifactCache, artifactOption, configScannerOption)
|
||||
func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
|
||||
v := _wireValue
|
||||
clientScanner := client.NewScanner(remoteScanOptions, v...)
|
||||
artifactArtifact, err := local2.NewArtifact(path, artifactCache, artifactOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, err
|
||||
}
|
||||
@@ -148,7 +154,7 @@ func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifac
|
||||
}
|
||||
|
||||
func initializeRemoteResultClient() result.Client {
|
||||
dbConfig := db.Config{}
|
||||
resultClient := result.NewClient(dbConfig)
|
||||
config := db.Config{}
|
||||
resultClient := result.NewClient(config)
|
||||
return resultClient
|
||||
}
|
||||
|
||||
17
pkg/commands/option/secret.go
Normal file
17
pkg/commands/option/secret.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// SecretOption holds the options for secret scanning
|
||||
type SecretOption struct {
|
||||
SecretConfigPath string
|
||||
}
|
||||
|
||||
// NewSecretOption is the factory method to return secret options
|
||||
func NewSecretOption(c *cli.Context) SecretOption {
|
||||
return SecretOption{
|
||||
SecretConfigPath: c.String("secret-config"),
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
ftypes "github.com/aquasecurity/fanal/types"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
@@ -42,33 +43,41 @@ func (tw TableWriter) write(result types.Result) {
|
||||
table := tablewriter.NewWriter(tw.Output)
|
||||
|
||||
var severityCount map[string]int
|
||||
if len(result.Vulnerabilities) != 0 {
|
||||
switch {
|
||||
case len(result.Vulnerabilities) != 0:
|
||||
severityCount = tw.writeVulnerabilities(table, result.Vulnerabilities)
|
||||
} else if len(result.Misconfigurations) != 0 {
|
||||
case len(result.Misconfigurations) != 0:
|
||||
severityCount = tw.writeMisconfigurations(table, result.Misconfigurations)
|
||||
case len(result.Secrets) != 0:
|
||||
severityCount = tw.writeSecrets(table, result.Secrets)
|
||||
}
|
||||
|
||||
total, summaries := tw.summary(severityCount)
|
||||
|
||||
target := result.Target
|
||||
if result.Class != types.ClassOSPkg {
|
||||
if result.Class == types.ClassSecret {
|
||||
if len(result.Secrets) == 0 {
|
||||
return
|
||||
}
|
||||
target += " (secrets)"
|
||||
} else if result.Class != types.ClassOSPkg {
|
||||
target += fmt.Sprintf(" (%s)", result.Type)
|
||||
}
|
||||
|
||||
fmt.Printf("\n%s\n", target)
|
||||
fmt.Println(strings.Repeat("=", len(target)))
|
||||
if result.MisconfSummary != nil {
|
||||
if result.Class == types.ClassConfig {
|
||||
// for misconfigurations
|
||||
summary := result.MisconfSummary
|
||||
fmt.Printf("Tests: %d (SUCCESSES: %d, FAILURES: %d, EXCEPTIONS: %d)\n",
|
||||
summary.Successes+summary.Failures+summary.Exceptions, summary.Successes, summary.Failures, summary.Exceptions)
|
||||
fmt.Printf("Failures: %d (%s)\n\n", total, strings.Join(summaries, ", "))
|
||||
} else {
|
||||
// for vulnerabilities
|
||||
// for vulnerabilities and secrets
|
||||
fmt.Printf("Total: %d (%s)\n\n", total, strings.Join(summaries, ", "))
|
||||
}
|
||||
|
||||
if len(result.Vulnerabilities) == 0 && len(result.Misconfigurations) == 0 {
|
||||
if len(result.Vulnerabilities) == 0 && len(result.Misconfigurations) == 0 && len(result.Secrets) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -134,6 +143,20 @@ func (tw TableWriter) writeMisconfigurations(table *tablewriter.Table, misconfs
|
||||
return severityCount
|
||||
}
|
||||
|
||||
func (tw TableWriter) writeSecrets(table *tablewriter.Table, secrets []ftypes.SecretFinding) map[string]int {
|
||||
table.SetColWidth(80)
|
||||
|
||||
alignment := []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER,
|
||||
tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT}
|
||||
header := []string{"Category", "Description", "Severity", "Line No", "Match"}
|
||||
|
||||
table.SetColumnAlignment(alignment)
|
||||
table.SetHeader(header)
|
||||
severityCount := tw.setSecretRows(table, secrets)
|
||||
|
||||
return severityCount
|
||||
}
|
||||
|
||||
func (tw TableWriter) setVulnerabilityRows(table *tablewriter.Table, vulns []types.DetectedVulnerability) map[string]int {
|
||||
severityCount := map[string]int{}
|
||||
for _, v := range vulns {
|
||||
@@ -212,6 +235,24 @@ func (tw TableWriter) setMisconfRows(table *tablewriter.Table, misconfs []types.
|
||||
return severityCount
|
||||
}
|
||||
|
||||
func (tw TableWriter) setSecretRows(table *tablewriter.Table, secrets []ftypes.SecretFinding) map[string]int {
|
||||
severityCount := map[string]int{}
|
||||
for _, secret := range secrets {
|
||||
severity := secret.Severity
|
||||
severityCount[severity]++
|
||||
if tw.Output == os.Stdout {
|
||||
severity = dbTypes.ColorizeSeverity(severity)
|
||||
}
|
||||
|
||||
row := []string{string(secret.Category), secret.Title, severity,
|
||||
fmt.Sprint(secret.StartLine), // multi-line is not supported for now.
|
||||
secret.Match}
|
||||
|
||||
table.Append(row)
|
||||
}
|
||||
return severityCount
|
||||
}
|
||||
|
||||
func (tw TableWriter) outputTrace(result types.Result) {
|
||||
blue := color.New(color.FgBlue).SprintFunc()
|
||||
green := color.New(color.FgGreen).SprintfFunc()
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
ftypes "github.com/aquasecurity/fanal/types"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
|
||||
@@ -131,24 +132,25 @@ func (c Client) getPrimaryURL(vulnID string, refs []string, source dbTypes.Sourc
|
||||
}
|
||||
|
||||
// Filter filter out the vulnerabilities
|
||||
func (c Client) Filter(ctx context.Context, vulns []types.DetectedVulnerability, misconfs []types.DetectedMisconfiguration,
|
||||
func (c Client) Filter(ctx context.Context, vulns []types.DetectedVulnerability, misconfs []types.DetectedMisconfiguration, secrets []ftypes.SecretFinding,
|
||||
severities []dbTypes.Severity, ignoreUnfixed, includeNonFailures bool, ignoreFile, policyFile string) (
|
||||
[]types.DetectedVulnerability, *types.MisconfSummary, []types.DetectedMisconfiguration, error) {
|
||||
[]types.DetectedVulnerability, *types.MisconfSummary, []types.DetectedMisconfiguration, []ftypes.SecretFinding, error) {
|
||||
ignoredIDs := getIgnoredIDs(ignoreFile)
|
||||
|
||||
filteredVulns := filterVulnerabilities(vulns, severities, ignoreUnfixed, ignoredIDs)
|
||||
misconfSummary, filteredMisconfs := filterMisconfigurations(misconfs, severities, includeNonFailures, ignoredIDs)
|
||||
filteredSecrets := filterSecrets(secrets, severities)
|
||||
|
||||
if policyFile != "" {
|
||||
var err error
|
||||
filteredVulns, filteredMisconfs, err = applyPolicy(ctx, filteredVulns, filteredMisconfs, policyFile)
|
||||
if err != nil {
|
||||
return nil, nil, nil, xerrors.Errorf("failed to apply the policy: %w", err)
|
||||
return nil, nil, nil, nil, xerrors.Errorf("failed to apply the policy: %w", err)
|
||||
}
|
||||
}
|
||||
sort.Sort(types.BySeverity(filteredVulns))
|
||||
|
||||
return filteredVulns, misconfSummary, filteredMisconfs, nil
|
||||
return filteredVulns, misconfSummary, filteredMisconfs, filteredSecrets, nil
|
||||
}
|
||||
|
||||
func filterVulnerabilities(vulns []types.DetectedVulnerability, severities []dbTypes.Severity,
|
||||
@@ -215,6 +217,20 @@ func filterMisconfigurations(misconfs []types.DetectedMisconfiguration, severiti
|
||||
return summary, filtered
|
||||
}
|
||||
|
||||
func filterSecrets(secrets []ftypes.SecretFinding, severities []dbTypes.Severity) []ftypes.SecretFinding {
|
||||
var filtered []ftypes.SecretFinding
|
||||
for _, secret := range secrets {
|
||||
// Filter secrets by severity
|
||||
for _, s := range severities {
|
||||
if s.String() == secret.Severity {
|
||||
filtered = append(filtered, secret)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func summarize(status types.MisconfStatus, summary *types.MisconfSummary) {
|
||||
switch status {
|
||||
case types.StatusFailure:
|
||||
|
||||
@@ -363,6 +363,7 @@ func TestClient_Filter(t *testing.T) {
|
||||
type args struct {
|
||||
vulns []types.DetectedVulnerability
|
||||
misconfs []types.DetectedMisconfiguration
|
||||
secrets []ftypes.SecretFinding
|
||||
severities []dbTypes.Severity
|
||||
ignoreUnfixed bool
|
||||
ignoreFile string
|
||||
@@ -374,6 +375,7 @@ func TestClient_Filter(t *testing.T) {
|
||||
wantVulns []types.DetectedVulnerability
|
||||
wantMisconfSummary *types.MisconfSummary
|
||||
wantMisconfs []types.DetectedMisconfiguration
|
||||
wantSecrets []ftypes.SecretFinding
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
@@ -443,6 +445,24 @@ func TestClient_Filter(t *testing.T) {
|
||||
Status: types.StatusPassed,
|
||||
},
|
||||
},
|
||||
secrets: []ftypes.SecretFinding{
|
||||
{
|
||||
RuleID: "generic-critical-rule",
|
||||
Severity: dbTypes.SeverityCritical.String(),
|
||||
Title: "Critical Secret should pass filter",
|
||||
StartLine: 1,
|
||||
EndLine: 2,
|
||||
Match: "*****",
|
||||
},
|
||||
{
|
||||
RuleID: "generic-low-rule",
|
||||
Severity: dbTypes.SeverityLow.String(),
|
||||
Title: "Low Secret should be ignored",
|
||||
StartLine: 3,
|
||||
EndLine: 4,
|
||||
Match: "*****",
|
||||
},
|
||||
},
|
||||
severities: []dbTypes.Severity{dbTypes.SeverityCritical, dbTypes.SeverityHigh, dbTypes.SeverityUnknown},
|
||||
ignoreUnfixed: false,
|
||||
},
|
||||
@@ -499,6 +519,16 @@ func TestClient_Filter(t *testing.T) {
|
||||
Status: types.StatusFailure,
|
||||
},
|
||||
},
|
||||
wantSecrets: []ftypes.SecretFinding{
|
||||
{
|
||||
RuleID: "generic-critical-rule",
|
||||
Severity: dbTypes.SeverityCritical.String(),
|
||||
Title: "Critical Secret should pass filter",
|
||||
StartLine: 1,
|
||||
EndLine: 2,
|
||||
Match: "*****",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path with ignore-unfixed",
|
||||
@@ -770,12 +800,13 @@ func TestClient_Filter(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := Client{}
|
||||
gotVulns, gotMisconfSummary, gotMisconfs, err := c.Filter(context.Background(), tt.args.vulns, tt.args.misconfs,
|
||||
gotVulns, gotMisconfSummary, gotMisconfs, gotSecrets, err := c.Filter(context.Background(), tt.args.vulns, tt.args.misconfs, tt.args.secrets,
|
||||
tt.args.severities, tt.args.ignoreUnfixed, false, tt.args.ignoreFile, tt.args.policyFile)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.wantVulns, gotVulns)
|
||||
assert.Equal(t, tt.wantMisconfSummary, gotMisconfSummary)
|
||||
assert.Equal(t, tt.wantMisconfs, gotMisconfs)
|
||||
assert.Equal(t, tt.wantSecrets, gotSecrets)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ type options struct {
|
||||
rpcClient rpc.Scanner
|
||||
}
|
||||
|
||||
type option func(*options)
|
||||
type Option func(*options)
|
||||
|
||||
// WithRPCClient takes rpc client for testability
|
||||
func WithRPCClient(c rpc.Scanner) option {
|
||||
func WithRPCClient(c rpc.Scanner) Option {
|
||||
return func(opts *options) {
|
||||
opts.rpcClient = c
|
||||
}
|
||||
@@ -40,7 +40,7 @@ type Scanner struct {
|
||||
}
|
||||
|
||||
// NewScanner is the factory method to return RPC Scanner
|
||||
func NewScanner(scannerOptions ScannerOption, opts ...option) Scanner {
|
||||
func NewScanner(scannerOptions ScannerOption, opts ...Option) Scanner {
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
|
||||
@@ -104,10 +104,16 @@ func (s Scanner) Scan(target string, artifactKey string, blobKeys []string, opti
|
||||
|
||||
// Scan IaC config files
|
||||
if slices.Contains(options.SecurityChecks, types.SecurityCheckConfig) {
|
||||
configResults := s.misconfsToResults(artifactDetail.Misconfigurations, options)
|
||||
configResults := s.misconfsToResults(artifactDetail.Misconfigurations)
|
||||
results = append(results, configResults...)
|
||||
}
|
||||
|
||||
// Scan secrets
|
||||
if slices.Contains(options.SecurityChecks, types.SecurityCheckSecret) {
|
||||
secretResults := s.secretsToResults(artifactDetail.Secrets)
|
||||
results = append(results, secretResults...)
|
||||
}
|
||||
|
||||
return results, artifactDetail.OS, nil
|
||||
}
|
||||
|
||||
@@ -236,7 +242,7 @@ func (s Scanner) scanLibrary(apps []ftypes.Application, options types.ScanOption
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s Scanner) misconfsToResults(misconfs []ftypes.Misconfiguration, options types.ScanOptions) types.Results {
|
||||
func (s Scanner) misconfsToResults(misconfs []ftypes.Misconfiguration) types.Results {
|
||||
log.Logger.Infof("Detected config files: %d", len(misconfs))
|
||||
var results types.Results
|
||||
for _, misconf := range misconfs {
|
||||
@@ -272,6 +278,20 @@ func (s Scanner) misconfsToResults(misconfs []ftypes.Misconfiguration, options t
|
||||
return results
|
||||
}
|
||||
|
||||
func (s Scanner) secretsToResults(secrets []ftypes.Secret) types.Results {
|
||||
var results types.Results
|
||||
for _, secret := range secrets {
|
||||
log.Logger.Debugf("Secret file: %s", secret.FilePath)
|
||||
|
||||
results = append(results, types.Result{
|
||||
Target: secret.FilePath,
|
||||
Class: types.ClassSecret,
|
||||
Secrets: secret.Findings,
|
||||
})
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func toDetectedMisconfiguration(res ftypes.MisconfResult, defaultSeverity dbTypes.Severity,
|
||||
status types.MisconfStatus, layer ftypes.Layer) types.DetectedMisconfiguration {
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ var StandaloneRepositorySet = wire.NewSet(
|
||||
// RemoteSuperSet is used in the client mode
|
||||
var RemoteSuperSet = wire.NewSet(
|
||||
client.NewScanner,
|
||||
wire.Value([]client.Option(nil)),
|
||||
wire.Bind(new(Driver), new(client.Scanner)),
|
||||
NewScanner,
|
||||
)
|
||||
|
||||
@@ -37,6 +37,7 @@ const (
|
||||
ClassOSPkg = "os-pkgs"
|
||||
ClassLangPkg = "lang-pkgs"
|
||||
ClassConfig = "config"
|
||||
ClassSecret = "secret"
|
||||
)
|
||||
|
||||
// Result holds a target and detected vulnerabilities
|
||||
@@ -48,6 +49,7 @@ type Result struct {
|
||||
Vulnerabilities []DetectedVulnerability `json:"Vulnerabilities,omitempty"`
|
||||
MisconfSummary *MisconfSummary `json:"MisconfSummary,omitempty"`
|
||||
Misconfigurations []DetectedMisconfiguration `json:"Misconfigurations,omitempty"`
|
||||
Secrets []ftypes.SecretFinding `json:"Secrets,omitempty"`
|
||||
CustomResources []ftypes.CustomResource `json:"CustomResources,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -28,11 +28,14 @@ const (
|
||||
|
||||
// SecurityCheckConfig is a security check of misconfigurations
|
||||
SecurityCheckConfig = SecurityCheck("config")
|
||||
|
||||
// SecurityCheckSecret is a security check of secrets
|
||||
SecurityCheckSecret = SecurityCheck("secret")
|
||||
)
|
||||
|
||||
var (
|
||||
vulnTypes = []string{VulnTypeOS, VulnTypeLibrary}
|
||||
securityChecks = []string{SecurityCheckVulnerability, SecurityCheckConfig}
|
||||
securityChecks = []string{SecurityCheckVulnerability, SecurityCheckConfig, SecurityCheckSecret}
|
||||
)
|
||||
|
||||
// NewVulnType returns an instance of VulnType
|
||||
|
||||
Reference in New Issue
Block a user