diff --git a/.github/workflows/semantic-pr.yaml b/.github/workflows/semantic-pr.yaml index 429e8ae05b..6e42953d2a 100644 --- a/.github/workflows/semantic-pr.yaml +++ b/.github/workflows/semantic-pr.yaml @@ -42,6 +42,7 @@ jobs: sbom server k8s + vm alpine redhat @@ -66,7 +67,7 @@ jobs: go c c++ - + os lang @@ -82,11 +83,11 @@ jobs: cli flag - + cyclonedx spdx helm report db - deps \ No newline at end of file + deps diff --git a/.github/workflows/vm-test.yaml b/.github/workflows/vm-test.yaml new file mode 100644 index 0000000000..f378cce508 --- /dev/null +++ b/.github/workflows/vm-test.yaml @@ -0,0 +1,27 @@ +name: VM Test +on: + push: + branches: + - main + paths: + - 'pkg/fanal/vm/**' + - 'pkg/fanal/walker/vm.go' + - 'pkg/fanal/artifact/vm/**' + - 'integration/vm_test.go' + pull_request: + +jobs: + vm-test: + name: VM Integration Test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version-file: go.mod + - name: Run vm integration tests + run: | + make test-vm-integration \ No newline at end of file diff --git a/.gitignore b/.gitignore index a6dc5509f6..1c15cd0f33 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ thumbs.db # test fixtures coverage.txt integration/testdata/fixtures/images +integration/testdata/fixtures/vm-images # SBOMs generated during CI /bom.json @@ -33,4 +34,4 @@ integration/testdata/fixtures/images dist # WebAssembly -*.wasm +*.wasm \ No newline at end of file diff --git a/Makefile b/Makefile index 34f18a02b5..902a12fc5e 100644 --- a/Makefile +++ b/Makefile @@ -77,6 +77,15 @@ test-integration: integration/testdata/fixtures/images/*.tar.gz test-module-integration: integration/testdata/fixtures/images/*.tar.gz $(EXAMPLE_MODULES) go test -v -tags=module_integration ./integration/... +# Run VM integration tests +.PHONY: test-vm-integration +test-vm-integration: integration/testdata/fixtures/vm-images/*.img.gz + go test -v -tags=vm_integration ./integration/... + +integration/testdata/fixtures/vm-images/*.img.gz: + integration/scripts/download-vm-images.sh + + .PHONY: lint lint: $(GOBIN)/golangci-lint $(GOBIN)/golangci-lint run --timeout 5m @@ -121,4 +130,4 @@ mkdocs-serve: # Generate JSON marshaler/unmarshaler for TinyGo/WebAssembly as TinyGo doesn't support encoding/json. .PHONY: easyjson easyjson: $(GOBIN)/easyjson - easyjson pkg/module/serialize/types.go + easyjson pkg/module/serialize/types.go \ No newline at end of file diff --git a/README.md b/README.md index 9c9486d29e..a1c96dcb20 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,15 @@ [πŸ“– Documentation][docs] -Trivy ([pronunciation][pronunciation]) is a comprehensive and versatile security scanner. Trivy has *scanners* that look for security issues, and *targets* where it can find those issues. +Trivy ([pronunciation][pronunciation]) is a comprehensive and versatile security scanner. +Trivy has *scanners* that look for security issues, and *targets* where it can find those issues. Targets (what Trivy can scan): - Container Image - Filesystem -- Git repository (remote) +- Git Repository (remote) +- Virtual Machine Image - Kubernetes - AWS diff --git a/docs/docs/index.md b/docs/docs/index.md index 48b46113c1..325396ba1c 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -45,7 +45,7 @@ This documentation details how to use Trivy to access the features listed below. Please see [LICENSE][license] for Trivy licensing information. -[installation]: ../index.md +[installation]: ../getting-started/installation.md [vuln]: ../docs/vulnerability/scanning/index.md [misconf]: ../docs/misconfiguration/scanning.md [kubernetesoperator]: ../docs/kubernetes/operator/index.md @@ -63,7 +63,7 @@ Please see [LICENSE][license] for Trivy licensing information. [lang]: ../docs/vulnerability/detection/language.md [builtin]: ../docs/misconfiguration/policy/builtin.md -[quickstart]: ../getting-started/quickstart.md +[quickstart]: ../index.md [podman]: ../docs/advanced/container/podman.md [sbom]: ../docs/sbom/index.md diff --git a/docs/docs/vm/aws.md b/docs/docs/vm/aws.md new file mode 100644 index 0000000000..3832085e14 --- /dev/null +++ b/docs/docs/vm/aws.md @@ -0,0 +1,70 @@ +# AWS EC2 + +Trivy can scan the following targets in AWS EC2. + +- Amazon Machine Image (AMI) +- Elastic Block Store (EBS) Snapshot + +## Amazon Machine Image (AMI) +You can specify your AMI ID with the `ami:` prefix. + +```shell +$ trivy vm ami:${your_ami_id} +``` + +!!! note + AMIs in the marketplace are not supported because the EBS direct APIs don't support that. + See [the AWS documentation][ebsapi-elements] for the detail. + +### Example + +```shell +$ trivy vm --security-checks vuln ami:ami-0123456789abcdefg +``` + +!!! tip + The scanning could be faster if you enable only vulnerability scanning (`--security-checks vuln`) because Trivy tries to download only necessary blocks for vulnerability detection. + + +### Required Actions +Some actions on EBS are also necessary since Trivy scans an EBS snapshot tied to the specified AMI under the hood. + +- ec2:DescribeImages +- ebs:ListSnapshotBlocks +- ebs:GetSnapshotBlock + +## Elastic Block Store (EBS) Snapshot +You can specify your EBS snapshot ID with the `ebs:` prefix. + +```shell +$ trivy vm ebs:${your_ebs_snapshot_id} +``` + +!!! note + Public snapshots are not supported because the EBS direct APIs don't support that. + See [the AWS documentation][ebsapi-elements] for the detail. + +### Example +```shell +$ trivy vm --security-checks vuln ebs:snap-0123456789abcdefg +``` + +!!! tip +The scanning could be faster if you enable only vulnerability scanning (`--security-checks vuln`) because Trivy tries to download only necessary blocks for vulnerability detection. + +The above command takes a while as it calls EBS API and fetches the EBS blocks. +If you want to scan the same snapshot several times, you can download the snapshot locally by using [coldsnap][coldsnap] maintained by AWS. +Then, Trivy can scan the local VM image file. + +```shell +$ coldsnap download snap-0123456789abcdefg disk.img +$ trivy vm ./disk.img +``` + +### Required Actions + +- ebs:ListSnapshotBlocks +- ebs:GetSnapshotBlock + +[ebsapi-elements]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-accessing-snapshot.html#ebsapi-elements +[coldsnap]: https://github.com/awslabs/coldsnap \ No newline at end of file diff --git a/docs/docs/vm/index.md b/docs/docs/vm/index.md new file mode 100644 index 0000000000..b643f5f755 --- /dev/null +++ b/docs/docs/vm/index.md @@ -0,0 +1,121 @@ +# Virtual Machine Image + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +## Scanning +Trivy supports VM image scanning for vulnerabilities, secrets, etc. +The following targets are currently supported: + +- Local file +- [AWS EC2][aws] + +To scan VM images, you can use the `vm` subcommand. + +### Local file +Pass the path to your local VM image file. + +```bash +$ trivy vm --security-checks vuln disk.vmdk +``` + +
+Result + +``` +disk.vmdk (amazon 2 (Karoo)) +=========================================================================================== +Total: 802 (UNKNOWN: 0, LOW: 17, MEDIUM: 554, HIGH: 221, CRITICAL: 10) + +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Library β”‚ Vulnerability β”‚ Severity β”‚ Installed Version β”‚ Fixed Version β”‚ Title β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ amazon-ssm-agent β”‚ CVE-2022-24675 β”‚ HIGH β”‚ 3.0.529.0-1.amzn2 β”‚ 3.1.1575.0-1.amzn2 β”‚ golang: encoding/pem: fix stack overflow in Decode β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ https://avd.aquasec.com/nvd/cve-2022-24675 β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ bind-export-libs β”‚ CVE-2021-25215 β”‚ β”‚ 32:9.11.4-26.P2.amzn2.4 β”‚ 32:9.11.4-26.P2.amzn2.5 β”‚ bind: An assertion check can fail while answering queries β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ for DNAME records... β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ https://avd.aquasec.com/nvd/cve-2021-25215 β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ CVE-2021-25214 β”‚ MEDIUM β”‚ β”‚ 32:9.11.4-26.P2.amzn2.5.2 β”‚ bind: Broken inbound incremental zone update (IXFR) can β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ cause named to terminate... β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ https://avd.aquasec.com/nvd/cve-2021-25214 β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ bind-libs β”‚ CVE-2021-25215 β”‚ HIGH β”‚ β”‚ 32:9.11.4-26.P2.amzn2.5 β”‚ bind: An assertion check can fail while answering queries β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ for DNAME records... β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ https://avd.aquasec.com/nvd/cve-2021-25215 β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ CVE-2021-25214 β”‚ MEDIUM β”‚ β”‚ 32:9.11.4-26.P2.amzn2.5.2 β”‚ bind: Broken inbound incremental zone update (IXFR) can β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ cause named to terminate... β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ https://avd.aquasec.com/nvd/cve-2021-25214 β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ bind-libs-lite β”‚ CVE-2021-25215 β”‚ HIGH β”‚ β”‚ 32:9.11.4-26.P2.amzn2.5 β”‚ bind: An assertion check can fail while answering queries β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ for DNAME records... β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ https://avd.aquasec.com/nvd/cve-2021-25215 β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ CVE-2021-25214 β”‚ MEDIUM β”‚ β”‚ 32:9.11.4-26.P2.amzn2.5.2 β”‚ bind: Broken inbound incremental zone update (IXFR) can β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ cause named to terminate... β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ https://avd.aquasec.com/nvd/cve-2021-25214 β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +... +``` + +
+ +### AWS EC2 + +See [here][aws] for the detail. + +## Supported architectures + +### Virtual machine images + +| Image format | Support | +|--------------|:-------:| +| VMDK | βœ” | +| OVA | | +| VHD | | +| VHDX | | +| QCOW2 | | + + +#### VMDK disk types + +| VMDK disk type | Support | +|-----------------------------|:-------:| +| streamOptimized | βœ” | +| monolithicSparse | | +| vmfs | | +| vmfsSparse | | +| twoGbMaxExtentSparse | | +| monolithicFlat | | +| twoGbMaxExtentFlat | | +| vmfsRaw | | +| fullDevice | | +| partitionedDevice | | +| vmfsRawDeviceMap | | +| vmfsPassthroughRawDeviceMap | | + +Reference: [VMware Virtual Disk Format 1.1.pdf][vmdk] + + +### Disk partitions + +| Disk format | Support | +|------------------------------|:-------:| +| Master boot record (MBR) | βœ” | +| Extended master boot record | | +| GUID partition table (GPT) | βœ” | +| Logical volume manager (LVM) | | + +### Filesystems + +| Filesystem format | Support | +|-------------------|:-------:| +| XFS | βœ” | +| EXT4 | βœ” | +| EXT2/3 | | +| ZFS | | + +[aws]: ./aws.md +[vmdk]: https://www.vmware.com/app/vmdk/?src=vmdk \ No newline at end of file diff --git a/docs/docs/vulnerability/scanning/index.md b/docs/docs/vulnerability/scanning/index.md index ae1943d147..416df44689 100644 --- a/docs/docs/vulnerability/scanning/index.md +++ b/docs/docs/vulnerability/scanning/index.md @@ -1,11 +1,9 @@ # Vulnerability Scanning -Trivy scans [Container Images][image], [Rootfs][rootfs], [Filesystem][fs], and [Git Repositories][repo] to detect vulnerabilities. - -![vulnerability][vuln] +Trivy scans [Container Images][image], [Rootfs][rootfs], [Filesystem][fs], [Virtual Machine Image][vm] and [Git Repositories][repo] to detect vulnerabilities. [image]: image.md [rootfs]: rootfs.md [fs]: filesystem.md [repo]: git-repository.md -[vuln]: ../../../imgs/vulnerability.png +[vm]: ../../vm/index.md diff --git a/docs/imgs/vulnerability.png b/docs/imgs/vulnerability.png deleted file mode 100644 index 0c197103c2..0000000000 Binary files a/docs/imgs/vulnerability.png and /dev/null differ diff --git a/docs/index.md b/docs/index.md index de610bc498..3555c1c10e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,7 +22,8 @@ Targets (what Trivy can scan): - Container Image - Filesystem -- Git repository (remote) +- Git Repository (remote) +- Virtual Machine Image - Kubernetes - AWS @@ -150,8 +151,8 @@ Trivy is an [Aqua Security][aquasec] open source project. Learn about our open source work and portfolio [here][oss]. Contact us about any matter by opening a GitHub Discussion [here][discussions] -[Ecosystem]: ./ecosystem/overview -[Installation]: getting-started/installation/ +[Ecosystem]: ./ecosystem/index.md +[Installation]: getting-started/installation.md [pronunciation]: #how-to-pronounce-the-name-trivy [aquasec]: https://aquasec.com @@ -160,4 +161,4 @@ Contact us about any matter by opening a GitHub Discussion [here][discussions] [Tutorials]: ./tutorials/overview [CLI]: ./docs -[Contributing]: ./contributing/issue +[Contributing]: ./community/contribute/issue diff --git a/go.mod b/go.mod index 5714608c47..0b3be6bd2b 100644 --- a/go.mod +++ b/go.mod @@ -23,8 +23,9 @@ require ( github.com/aquasecurity/trivy-kubernetes v0.3.1-0.20221021174315-8d74450b4506 github.com/aws/aws-sdk-go v1.44.136 github.com/aws/aws-sdk-go-v2 v1.17.1 - github.com/aws/aws-sdk-go-v2/config v1.18.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.17.2 + github.com/aws/aws-sdk-go-v2/config v1.18.3 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.63.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.17.5 github.com/caarlos0/env/v6 v6.10.1 github.com/cenkalti/backoff v2.2.1+incompatible github.com/cheggaaa/pb/v3 v3.1.0 @@ -42,13 +43,19 @@ require ( github.com/google/uuid v1.3.0 github.com/google/wire v0.5.0 github.com/hashicorp/go-getter v1.6.2 + github.com/hashicorp/golang-lru v0.5.4 github.com/in-toto/in-toto-golang v0.5.0 github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075 github.com/kylelemons/godebug v1.1.0 github.com/mailru/easyjson v0.7.7 + github.com/masahiro331/go-disk v0.0.0-20220919035250-c8da316f91ac + github.com/masahiro331/go-ebs-file v0.0.0-20221125181850-09c63351e38c + github.com/masahiro331/go-ext4-filesystem v0.0.0-20221016160854-4b40d7ee6193 github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 + github.com/masahiro331/go-vmdk-parser v0.0.0-20221124162251-5eeffd974e5a + github.com/masahiro331/go-xfs-filesystem v0.0.0-20221123035428-5c173df8b3f6 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/open-policy-agent/opa v0.44.1-0.20220927105354-00e835a7cc15 github.com/owenrumney/go-sarif/v2 v2.1.2 @@ -68,7 +75,7 @@ require ( go.etcd.io/bbolt v1.3.6 go.uber.org/zap v1.23.0 golang.org/x/exp v0.0.0-20220823124025-807a23277127 - golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v3 v3.0.1 k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed @@ -77,7 +84,7 @@ require ( require ( github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.0 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.3 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.19 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.25 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.19 // indirect @@ -94,7 +101,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/codebuild v1.19.17 // indirect github.com/aws/aws-sdk-go-v2/service/docdb v1.19.11 // indirect github.com/aws/aws-sdk-go-v2/service/dynamodb v1.17.1 // indirect - github.com/aws/aws-sdk-go-v2/service/ec2 v1.63.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ebs v1.15.19 // indirect github.com/aws/aws-sdk-go-v2/service/ecr v1.17.18 // indirect github.com/aws/aws-sdk-go-v2/service/ecs v1.18.23 // indirect github.com/aws/aws-sdk-go-v2/service/efs v1.17.15 // indirect @@ -138,6 +145,7 @@ require ( github.com/liamg/iamgo v0.0.9 // indirect github.com/liamg/jfather v0.0.7 // indirect github.com/liamg/memoryfs v1.4.3 // indirect + github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect diff --git a/go.sum b/go.sum index 9db5f8289a..1723270e2b 100644 --- a/go.sum +++ b/go.sum @@ -239,10 +239,10 @@ github.com/aws/aws-sdk-go-v2 v1.17.1 h1:02c72fDJr87N8RAC2s3Qu0YuvMRZKNZJ9F+lAehC github.com/aws/aws-sdk-go-v2 v1.17.1/go.mod h1:JLnGeGONAyi2lWXI1p0PCIOIy333JMVK1U7Hf0aRFLw= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8 h1:tcFliCWne+zOuUfKNRn8JdFBuWPDuISDH08wD2ULkhk= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8/go.mod h1:JTnlBSot91steJeti4ryyu/tLd4Sk84O5W22L7O2EQU= -github.com/aws/aws-sdk-go-v2/config v1.18.0 h1:ULASZmfhKR/QE9UeZ7mzYjUzsnIydy/K1YMT6uH1KC0= -github.com/aws/aws-sdk-go-v2/config v1.18.0/go.mod h1:H13DRX9Nv5tAcQvPABrE3dm5XnLp1RC7fVSM3OWiLvA= -github.com/aws/aws-sdk-go-v2/credentials v1.13.0 h1:W5f73j1qurASap+jdScUo4aGzSXxaC7wq1i7CiwhvU8= -github.com/aws/aws-sdk-go-v2/credentials v1.13.0/go.mod h1:prZpUfBu1KZLBLVX482Sq4DpDXGugAre08TPEc21GUg= +github.com/aws/aws-sdk-go-v2/config v1.18.3 h1:3kfBKcX3votFX84dm00U8RGA1sCCh3eRMOGzg5dCWfU= +github.com/aws/aws-sdk-go-v2/config v1.18.3/go.mod h1:BYdrbeCse3ZnOD5+2/VE/nATOK8fEUpBtmPMdKSyhMU= +github.com/aws/aws-sdk-go-v2/credentials v1.13.3 h1:ur+FHdp4NbVIv/49bUjBW+FE7e57HOo03ELodttmagk= +github.com/aws/aws-sdk-go-v2/credentials v1.13.3/go.mod h1:/rOMmqYBcFfNbRPU0iN9IgGqD5+V2yp3iWNmIlz0wI4= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.19 h1:E3PXZSI3F2bzyj6XxUXdTIfvp425HHhwKsFvmzBwHgs= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.19/go.mod h1:VihW95zQpeKQWVPGkwT+2+WJNQV8UXFfMTWdU6VErL8= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23/go.mod h1:2DFxAQ9pfIRy0imBCJv+vZ2X6RKxves6fbnEuSry6b4= @@ -277,6 +277,8 @@ github.com/aws/aws-sdk-go-v2/service/docdb v1.19.11 h1:+jNOF3BdrSwCHWHU+lXYR78DC github.com/aws/aws-sdk-go-v2/service/docdb v1.19.11/go.mod h1:p2/C5LVvGstUjTb0z0qQNDf356iVEDrAMOvFJAkJQbA= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.17.1 h1:1QpTkQIAaZpR387it1L+erjB5bStGFCJRvmXsodpPEU= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.17.1/go.mod h1:BZhn/C3z13ULTSstVi2Kymc62bgjFh/JwLO9Tm2OFYI= +github.com/aws/aws-sdk-go-v2/service/ebs v1.15.19 h1:6S06aB1xyXs3C9RE5RyJROw1v1ByXGHo/cxTZ13VRp0= +github.com/aws/aws-sdk-go-v2/service/ebs v1.15.19/go.mod h1:pJhytP5qZaPIqCF2BewXttD4bc29KIPm6LMSIBhMCFI= github.com/aws/aws-sdk-go-v2/service/ec2 v1.63.1 h1:jSS5gynKz4XaGcs6m25idCTN+tvPkRJ2WedSWCcZEjI= github.com/aws/aws-sdk-go-v2/service/ec2 v1.63.1/go.mod h1:0+6fPoY0SglgzQUs2yml7X/fup12cMlVumJufh5npRQ= github.com/aws/aws-sdk-go-v2/service/ecr v1.17.18 h1:uiF/RI+Up8H2xdgT2GWa20YzxiKEalHieqNjm6HC3Xk= @@ -336,8 +338,8 @@ github.com/aws/aws-sdk-go-v2/service/sso v1.11.25 h1:GFZitO48N/7EsFDt8fMa5iYdmWq github.com/aws/aws-sdk-go-v2/service/sso v1.11.25/go.mod h1:IARHuzTXmj1C0KS35vboR0FeJ89OkEy1M9mWbK2ifCI= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.8 h1:jcw6kKZrtNfBPJkaHrscDOZoe5gvi9wjudnxvozYFJo= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.8/go.mod h1:er2JHN+kBY6FcMfcBBKNGCT3CarImmdFzishsqBmSRI= -github.com/aws/aws-sdk-go-v2/service/sts v1.17.2 h1:tpwEMRdMf2UsplengAOnmSIRdvAxf75oUFR+blBr92I= -github.com/aws/aws-sdk-go-v2/service/sts v1.17.2/go.mod h1:bXcN3koeVYiJcdDU89n3kCYILob7Y34AeLopUbZgLT4= +github.com/aws/aws-sdk-go-v2/service/sts v1.17.5 h1:60SJ4lhvn///8ygCzYy2l53bFW/Q15bVfyjyAWo6zuw= +github.com/aws/aws-sdk-go-v2/service/sts v1.17.5/go.mod h1:bXcN3koeVYiJcdDU89n3kCYILob7Y34AeLopUbZgLT4= github.com/aws/aws-sdk-go-v2/service/workspaces v1.23.0 h1:lrgZ9pZm9utPOPAXmQhqtf8oWRRksoSFxOE8RoD+pHc= github.com/aws/aws-sdk-go-v2/service/workspaces v1.23.0/go.mod h1:vPam8+zGthTXeaFWgl3Uqbzo/0QEoXF22jpuMZ97hSk= github.com/aws/smithy-go v1.13.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= @@ -972,6 +974,8 @@ github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl/v2 v2.14.1 h1:x0BpjfZ+CYdbiz+8yZTQ+gdLO7IXvOut7Da+XJayx34= @@ -1094,6 +1098,8 @@ github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= +github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc= +github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= @@ -1113,8 +1119,18 @@ github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2 github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/masahiro331/go-disk v0.0.0-20220919035250-c8da316f91ac h1:QyRucnGOLHJag1eB9CtuZwZk+/LpvTSYr5mnFLLFlgA= +github.com/masahiro331/go-disk v0.0.0-20220919035250-c8da316f91ac/go.mod h1:J7Vb0sf0JzOhT0uHTeCqO6dqP/ELVcQvQ6yQ/56ZRGw= +github.com/masahiro331/go-ebs-file v0.0.0-20221125181850-09c63351e38c h1:sBVxQbdRxqMoczqQYPxUwe3V3msmAUj9KPuPZRpD13k= +github.com/masahiro331/go-ebs-file v0.0.0-20221125181850-09c63351e38c/go.mod h1:5NOkqebMwu8UiOTSjwqam1Ykdr7fci52TVE2xDQnIiM= +github.com/masahiro331/go-ext4-filesystem v0.0.0-20221016160854-4b40d7ee6193 h1:1005OfmUUdBL4540DpkovaDrV+lkiEda7Nb/t792LEg= +github.com/masahiro331/go-ext4-filesystem v0.0.0-20221016160854-4b40d7ee6193/go.mod h1:X08d9nmB+eg7Gj2XWAOkiG8lbMFbgGXPsDKEvkFwyF8= github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 h1:AevUBW4cc99rAF8q8vmddIP8qd/0J5s/UyltGbp66dg= github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08/go.mod h1:JOkBRrE1HvgTyjk6diFtNGgr8XJMtIfiBzkL5krqzVk= +github.com/masahiro331/go-vmdk-parser v0.0.0-20221124162251-5eeffd974e5a h1:R2wjAgJt2IwTqKfSDc4mJ0SIz66lIslqmAjW/Dmi5fE= +github.com/masahiro331/go-vmdk-parser v0.0.0-20221124162251-5eeffd974e5a/go.mod h1:5f7mCJGW9cJb8SDn3z8qodGxpMCOo8d/2nls/tiwRrw= +github.com/masahiro331/go-xfs-filesystem v0.0.0-20221123035428-5c173df8b3f6 h1:UHo9uZWmmUVmYUJqTXPSHNUnPaO6ofKKdTWpEQtpxho= +github.com/masahiro331/go-xfs-filesystem v0.0.0-20221123035428-5c173df8b3f6/go.mod h1:QKBZqdn6teT0LK3QhAf3K6xakItd1LonOShOEC44idQ= github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -2016,8 +2032,9 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= diff --git a/integration/integration_test.go b/integration/integration_test.go index 95b5c0cb50..627f011d57 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -1,4 +1,4 @@ -//go:build integration || module_integration +//go:build integration || vm_integration || module_integration package integration diff --git a/integration/scripts/download-vm-images.sh b/integration/scripts/download-vm-images.sh new file mode 100755 index 0000000000..4a6307ad79 --- /dev/null +++ b/integration/scripts/download-vm-images.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +TEST_VM=ghcr.io/aquasecurity/trivy-test-vm-images + +CRANE_IMG=gcr.io/go-containerregistry/crane:v0.12.1 +ORAS_IMG=ghcr.io/oras-project/oras:v0.16.0 + +CURRENT=$(cd $(dirname $0);pwd) + +mkdir -p ${CURRENT}/../testdata/fixtures/vm-images/ + +# List the tags +TAGS=$(docker run --rm ${CRANE_IMG} ls ${TEST_VM}) + +# Download missing images +for tag in $TAGS +do + dir=${CURRENT}/../testdata/fixtures/vm-images/ + if [ ! -e "${dir}/${tag}.img.gz" ] || [ ! -e "${dir}/${tag}.vmdk.gz" ]; then + echo "Downloading $tag..." + echo "oras pull ${TEST_VM}:${tag}" + docker run --rm -v ${dir}:/workspace ${ORAS_IMG} pull "${TEST_VM}:${tag}" + fi +done \ No newline at end of file diff --git a/integration/testdata/amazonlinux2-gp2-x86-vm.json.golden b/integration/testdata/amazonlinux2-gp2-x86-vm.json.golden new file mode 100644 index 0000000000..394d298650 --- /dev/null +++ b/integration/testdata/amazonlinux2-gp2-x86-vm.json.golden @@ -0,0 +1,66 @@ +{ + "SchemaVersion": 2, + "ArtifactName": "disk.img", + "ArtifactType": "vm", + "Metadata": { + "OS": { + "Family": "amazon", + "Name": "2 (Karoo)" + }, + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "disk.img (amazon 2 (Karoo))", + "Class": "os-pkgs", + "Type": "amazon", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2022-21233", + "PkgID": "microcode_ctl@2.1-47.amzn2.0.12.x86_64", + "PkgName": "microcode_ctl", + "InstalledVersion": "2:2.1-47.amzn2.0.12", + "FixedVersion": "2:2.1-47.amzn2.0.13", + "Layer": {}, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-21233", + "DataSource": { + "ID": "amazon", + "Name": "Amazon Linux Security Center", + "URL": "https://alas.aws.amazon.com/" + }, + "Title": "hw: cpu: Intel: Stale Data Read from legacy xAPIC vulnerability", + "Description": "Improper isolation of shared resources in some Intel(R) Processors may allow", + "Severity": "MEDIUM", + "CVSS": { + "nvd": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", + "V3Score": 5.5 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N", + "V3Score": 6 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2022-21233", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-21233", + "https://security.netapp.com/advisory/ntap-20220923-0002/", + "https://ubuntu.com/security/notices/USN-5612-1" + ], + "PublishedDate": "2022-08-18T20:15:00Z", + "LastModifiedDate": "2022-09-23T15:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/fixtures/db/amazon.yaml b/integration/testdata/fixtures/db/amazon.yaml index 79da564d41..7fc55859d0 100644 --- a/integration/testdata/fixtures/db/amazon.yaml +++ b/integration/testdata/fixtures/db/amazon.yaml @@ -17,4 +17,9 @@ FixedVersion: 7.61.1-11.amzn2.0.2 - key: CVE-2019-5481 value: - FixedVersion: 7.61.1-12.amzn2.0.1 \ No newline at end of file + FixedVersion: 7.61.1-12.amzn2.0.1 + - bucket: microcode_ctl + pairs: + - key: CVE-2022-21233 + value: + FixedVersion: 2:2.1-47.amzn2.0.13 diff --git a/integration/testdata/fixtures/db/redhat.yaml b/integration/testdata/fixtures/db/redhat.yaml index b4787bc1e4..7ce30f53b1 100644 --- a/integration/testdata/fixtures/db/redhat.yaml +++ b/integration/testdata/fixtures/db/redhat.yaml @@ -72,4 +72,4 @@ - 857 - 858 Cves: - - Severity: 2.0 + - Severity: 2.0 \ No newline at end of file diff --git a/integration/testdata/fixtures/db/ubuntu.yaml b/integration/testdata/fixtures/db/ubuntu.yaml index 9a00f65cc9..5f0f42442a 100644 --- a/integration/testdata/fixtures/db/ubuntu.yaml +++ b/integration/testdata/fixtures/db/ubuntu.yaml @@ -12,3 +12,10 @@ - key: CVE-2019-5094 value: FixedVersion: 1.44.1-1ubuntu1.2 +- bucket: ubuntu 22.04 + pairs: + - bucket: bind9 + pairs: + - key: CVE-2022-2795 + value: + FixedVersion: 1:9.18.1-1ubuntu1.2 \ No newline at end of file diff --git a/integration/testdata/fixtures/db/vulnerability.yaml b/integration/testdata/fixtures/db/vulnerability.yaml index c8b82b8f12..d136a075ec 100644 --- a/integration/testdata/fixtures/db/vulnerability.yaml +++ b/integration/testdata/fixtures/db/vulnerability.yaml @@ -1,5 +1,61 @@ - bucket: vulnerability pairs: + - key: CVE-2022-21233 + value: + Title: "hw: cpu: Intel: Stale Data Read from legacy xAPIC vulnerability" + Description: "Improper isolation of shared resources in some Intel(R) Processors may allow" + Severity: MEDIUM + CVSS: + nvd: + V3Score: 5.5 + V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N" + redhat: + V3Score: 6.0 + V3Vector: "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N" + LastModifiedDate: "2022-09-23T15:15:00Z" + PublishedDate: "2022-08-18T20:15:00Z" + References: + - "https://access.redhat.com/security/cve/CVE-2022-21233" + - "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-21233" + - "https://security.netapp.com/advisory/ntap-20220923-0002/" + - "https://ubuntu.com/security/notices/USN-5612-1" + VendorSeverity: + arch-linux: 2 + nvd: 2 + redhat: 2 + ubuntu: 2 + - key: CVE-2022-2795 + value: + Title: "bind: processing large delegations may severely degrade resolver performance" + Severity: HIGH + Description: By flooding the target resolver with queries exploiting this flaw an attacker + CVSS: + nvd: + V3Score: 7.5 + V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H + redhat: + V3Score: 5.3 + V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L + CweIDs: + - CWE-400 + LastModifiedDate: 2022-10-06T20:15:00Z + PublishedDate: 2022-09-21T11:15:00Z + References: + - http://www.openwall.com/lists/oss-security/2022/09/21/3 + - https://access.redhat.com/security/cve/CVE-2022-2795 + - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-2795 + - https://kb.isc.org/docs/cve-2022-2795 + - https://lists.debian.org/debian-lts-announce/2022/10/msg00007.html + - https://nvd.nist.gov/vuln/detail/CVE-2022-2795 + - https://ubuntu.com/security/notices/USN-5626-1 + - https://ubuntu.com/security/notices/USN-5626-2 + - https://www.debian.org/security/2022/dsa-5235 + VendorSeverity: + cbl-mariner: 3.0 + nvd: 3.0 + photon: 3.0 + redhat: 1.0 + ubuntu: 2.0 - key: CVE-2016-9401 value: CVSS: @@ -1247,4 +1303,4 @@ - "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-14155", - "https://nvd.nist.gov/vuln/detail/CVE-2020-14155" PublishedDate: "2020-06-15T17:15:00Z" - LastModifiedDate: "2022-04-28T15:06:00Z" \ No newline at end of file + LastModifiedDate: "2022-04-28T15:06:00Z" diff --git a/integration/testdata/ubuntu-gp2-x86-vm.json.golden b/integration/testdata/ubuntu-gp2-x86-vm.json.golden new file mode 100644 index 0000000000..3ee587cc9d --- /dev/null +++ b/integration/testdata/ubuntu-gp2-x86-vm.json.golden @@ -0,0 +1,147 @@ +{ + "SchemaVersion": 2, + "ArtifactName": "disk.img", + "ArtifactType": "vm", + "Metadata": { + "OS": { + "Family": "ubuntu", + "Name": "22.04" + }, + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "disk.img (ubuntu 22.04)", + "Class": "os-pkgs", + "Type": "ubuntu", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2022-2795", + "PkgID": "bind9-dnsutils@1:9.18.1-1ubuntu1.1", + "PkgName": "bind9-dnsutils", + "InstalledVersion": "1:9.18.1-1ubuntu1.1", + "FixedVersion": "1:9.18.1-1ubuntu1.2", + "Layer": {}, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-2795", + "Title": "bind: processing large delegations may severely degrade resolver performance", + "Description": "By flooding the target resolver with queries exploiting this flaw an attacker", + "Severity": "HIGH", + "CweIDs": [ + "CWE-400" + ], + "CVSS": { + "nvd": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V3Score": 7.5 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "V3Score": 5.3 + } + }, + "References": [ + "http://www.openwall.com/lists/oss-security/2022/09/21/3", + "https://access.redhat.com/security/cve/CVE-2022-2795", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-2795", + "https://kb.isc.org/docs/cve-2022-2795", + "https://lists.debian.org/debian-lts-announce/2022/10/msg00007.html", + "https://nvd.nist.gov/vuln/detail/CVE-2022-2795", + "https://ubuntu.com/security/notices/USN-5626-1", + "https://ubuntu.com/security/notices/USN-5626-2", + "https://www.debian.org/security/2022/dsa-5235" + ], + "PublishedDate": "2022-09-21T11:15:00Z", + "LastModifiedDate": "2022-10-06T20:15:00Z" + }, + { + "VulnerabilityID": "CVE-2022-2795", + "PkgID": "bind9-host@1:9.18.1-1ubuntu1.1", + "PkgName": "bind9-host", + "InstalledVersion": "1:9.18.1-1ubuntu1.1", + "FixedVersion": "1:9.18.1-1ubuntu1.2", + "Layer": {}, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-2795", + "Title": "bind: processing large delegations may severely degrade resolver performance", + "Description": "By flooding the target resolver with queries exploiting this flaw an attacker", + "Severity": "HIGH", + "CweIDs": [ + "CWE-400" + ], + "CVSS": { + "nvd": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V3Score": 7.5 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "V3Score": 5.3 + } + }, + "References": [ + "http://www.openwall.com/lists/oss-security/2022/09/21/3", + "https://access.redhat.com/security/cve/CVE-2022-2795", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-2795", + "https://kb.isc.org/docs/cve-2022-2795", + "https://lists.debian.org/debian-lts-announce/2022/10/msg00007.html", + "https://nvd.nist.gov/vuln/detail/CVE-2022-2795", + "https://ubuntu.com/security/notices/USN-5626-1", + "https://ubuntu.com/security/notices/USN-5626-2", + "https://www.debian.org/security/2022/dsa-5235" + ], + "PublishedDate": "2022-09-21T11:15:00Z", + "LastModifiedDate": "2022-10-06T20:15:00Z" + }, + { + "VulnerabilityID": "CVE-2022-2795", + "PkgID": "bind9-libs@1:9.18.1-1ubuntu1.1", + "PkgName": "bind9-libs", + "InstalledVersion": "1:9.18.1-1ubuntu1.1", + "FixedVersion": "1:9.18.1-1ubuntu1.2", + "Layer": {}, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-2795", + "Title": "bind: processing large delegations may severely degrade resolver performance", + "Description": "By flooding the target resolver with queries exploiting this flaw an attacker", + "Severity": "HIGH", + "CweIDs": [ + "CWE-400" + ], + "CVSS": { + "nvd": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V3Score": 7.5 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "V3Score": 5.3 + } + }, + "References": [ + "http://www.openwall.com/lists/oss-security/2022/09/21/3", + "https://access.redhat.com/security/cve/CVE-2022-2795", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-2795", + "https://kb.isc.org/docs/cve-2022-2795", + "https://lists.debian.org/debian-lts-announce/2022/10/msg00007.html", + "https://nvd.nist.gov/vuln/detail/CVE-2022-2795", + "https://ubuntu.com/security/notices/USN-5626-1", + "https://ubuntu.com/security/notices/USN-5626-2", + "https://www.debian.org/security/2022/dsa-5235" + ], + "PublishedDate": "2022-09-21T11:15:00Z", + "LastModifiedDate": "2022-10-06T20:15:00Z" + } + ] + } + ] +} diff --git a/integration/vm_test.go b/integration/vm_test.go new file mode 100644 index 0000000000..9f51e65d72 --- /dev/null +++ b/integration/vm_test.go @@ -0,0 +1,112 @@ +//go:build vm_integration + +package integration + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestVM(t *testing.T) { + type args struct { + input string + format string + artifactType string + } + tests := []struct { + name string + args args + golden string + override types.Report + }{ + { + name: "amazon linux 2 in VMDK, filesystem XFS", + args: args{ + input: "testdata/fixtures/vm-images/amazon-2.vmdk.gz", + format: "json", + artifactType: "vm", + }, + golden: "testdata/amazonlinux2-gp2-x86-vm.json.golden", + }, + { + name: "amazon linux 2 in Snapshot, filesystem XFS", + args: args{ + input: "testdata/fixtures/vm-images/amazon-2.img.gz", + format: "json", + artifactType: "vm", + }, + golden: "testdata/amazonlinux2-gp2-x86-vm.json.golden", + }, + { + name: "Ubuntu in Snapshot, filesystem EXT4", + args: args{ + input: "testdata/fixtures/vm-images/ubuntu-2204.img.gz", + format: "json", + artifactType: "vm", + }, + golden: "testdata/ubuntu-gp2-x86-vm.json.golden", + }, + { + name: "Ubuntu in VMDK, filesystem EXT4", + args: args{ + input: "testdata/fixtures/vm-images/ubuntu-2204.vmdk.gz", + format: "json", + artifactType: "vm", + }, + golden: "testdata/ubuntu-gp2-x86-vm.json.golden", + }, + } + + // Set up testing DB + cacheDir := initDB(t) + + // Keep the current working directory + currentDir, err := os.Getwd() + require.NoError(t, err) + + const imageFile = "disk.img" + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + osArgs := []string{ + "--cache-dir", cacheDir, "vm", "--security-checks", "vuln", "-q", "--skip-db-update", + "--format", tt.args.format, + } + + tmpDir := t.TempDir() + + // Set up the output file + outputFile := filepath.Join(tmpDir, "output.json") + if *update { + outputFile = tt.golden + } + + // Get the absolute path of the golden file + goldenFile, err := filepath.Abs(tt.golden) + require.NoError(t, err) + + // Decompress the gzipped image file + imagePath := filepath.Join(tmpDir, imageFile) + testutil.DecompressGzip(t, tt.args.input, imagePath) + + // Change the current working directory so that targets in the result could be the same as golden files. + err = os.Chdir(tmpDir) + require.NoError(t, err) + defer os.Chdir(currentDir) + + osArgs = append(osArgs, "--output", outputFile) + osArgs = append(osArgs, imageFile) + + // Run "trivy vm" + err = execute(osArgs) + require.NoError(t, err) + compareReports(t, goldenFile, outputFile) + }) + } +} diff --git a/internal/testutil/gzip.go b/internal/testutil/gzip.go new file mode 100644 index 0000000000..268c17e50a --- /dev/null +++ b/internal/testutil/gzip.go @@ -0,0 +1,28 @@ +package testutil + +import ( + "compress/gzip" + "io" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +const max = int64(10) << 30 // 10GB + +func DecompressGzip(t *testing.T, src, dst string) { + w, err := os.Create(dst) + require.NoError(t, err) + defer w.Close() + + f, err := os.Open(src) + require.NoError(t, err) + defer f.Close() + + gr, err := gzip.NewReader(f) + require.NoError(t, err) + + _, err = io.CopyN(w, gr, max) + require.ErrorIs(t, err, io.EOF) +} diff --git a/mkdocs.yml b/mkdocs.yml index 337e6028cc..4c59f80006 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -89,10 +89,13 @@ nav: - Compliance: docs/kubernetes/cli/compliance.md - Operator: - Overview: docs/kubernetes/operator/index.md - - Compliance: - - Reports: docs/compliance/compliance.md - Cloud: - AWS: docs/cloud/aws/scanning.md + - Virtual Machine Image: + - Overview: docs/vm/index.md + - AWS EC2: docs/vm/aws.md + - Compliance: + - Reports: docs/compliance/compliance.md - SBOM: - Overview: docs/sbom/index.md - CycloneDX: docs/sbom/cyclonedx.md diff --git a/pkg/commands/app.go b/pkg/commands/app.go index bfd83d4898..416c8ec503 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -87,6 +87,7 @@ func NewApp(version string) *cobra.Command { NewSBOMCommand(globalFlags), NewVersionCommand(globalFlags), NewAWSCommand(globalFlags), + NewVMCommand(globalFlags), ) rootCmd.AddCommand(loadPluginCommands()...) @@ -824,7 +825,7 @@ func NewAWSCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { Use: "aws [flags]", Aliases: []string{}, Args: cobra.ExactArgs(0), - Short: "scan aws account", + Short: "[EXPERIMENTAL] Scan AWS account", Long: fmt.Sprintf(`Scan an AWS account for misconfigurations. Trivy uses the same authentication methods as the AWS CLI. See https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html The following services are supported: @@ -869,6 +870,62 @@ The following services are supported: return cmd } +func NewVMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + reportFlagGroup := flag.NewReportFlagGroup() + reportFlagGroup.ReportFormat = nil // TODO: support --report summary + + vmFlags := &flag.Flags{ + CacheFlagGroup: flag.NewCacheFlagGroup(), + DBFlagGroup: flag.NewDBFlagGroup(), + LicenseFlagGroup: flag.NewLicenseFlagGroup(), + MisconfFlagGroup: flag.NewMisconfFlagGroup(), + RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode + ReportFlagGroup: reportFlagGroup, + ScanFlagGroup: flag.NewScanFlagGroup(), + SecretFlagGroup: flag.NewSecretFlagGroup(), + VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), + } + + cmd := &cobra.Command{ + Use: "vm [flags] VM_IMAGE", + Aliases: []string{}, + Short: "[EXPERIMENTAL] Scan a virtual machine image", + Example: ` # Scan your AWS AMI + $ trivy vm --security-checks vuln ami:${your_ami_id} + + # Scan your AWS EBS snapshot + $ trivy vm ebs:${your_ebs_snapshot_id} +`, + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := vmFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + return validateArgs(cmd, args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := vmFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + options, err := vmFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter) + if err != nil { + return xerrors.Errorf("flag error: %w", err) + } + if options.Timeout < time.Minute*30 { + options.Timeout = time.Minute * 30 + log.Logger.Debug("Timeout is set to less than 30 min - upgrading to 30 min for this command.") + } + return artifact.Run(cmd.Context(), options, artifact.TargetVM) + }, + SilenceErrors: true, + SilenceUsage: true, + } + cmd.SetFlagErrorFunc(flagErrorFunc) + vmFlags.AddFlags(cmd) + cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, vmFlags.Usages(cmd))) + + return cmd +} + func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { reportFlagGroup := flag.NewReportFlagGroup() reportFlagGroup.DependencyTree = nil // disable '--dependency-tree' diff --git a/pkg/commands/artifact/inject.go b/pkg/commands/artifact/inject.go index 244072beec..6012de1e05 100644 --- a/pkg/commands/artifact/inject.go +++ b/pkg/commands/artifact/inject.go @@ -57,6 +57,13 @@ func initializeSBOMScanner(ctx context.Context, filePath string, artifactCache c return scanner.Scanner{}, nil, nil } +func initializeVMScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, + localArtifactCache cache.Cache, artifactOption artifact.Option) ( + scanner.Scanner, func(), error) { + wire.Build(scanner.StandaloneVMSet) + return scanner.Scanner{}, nil, nil +} + ///////////////// // Client/Server ///////////////// @@ -91,3 +98,10 @@ func initializeRemoteSBOMScanner(ctx context.Context, path string, artifactCache wire.Build(scanner.RemoteSBOMSet) return scanner.Scanner{}, nil, nil } + +// initializeRemoteVMScanner is for vm scanning in client/server mode +func initializeRemoteVMScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, + remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + wire.Build(scanner.RemoteVMSet) + return scanner.Scanner{}, nil, nil +} diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 927324bedd..36faa2ca0e 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -40,6 +40,7 @@ const ( TargetRepository TargetKind = "repo" TargetImageArchive TargetKind = "archive" TargetSBOM TargetKind = "sbom" + TargetVM TargetKind = "vm" devVersion = "dev" ) @@ -78,6 +79,8 @@ type Runner interface { ScanRepository(ctx context.Context, opts flag.Options) (types.Report, error) // ScanSBOM scans SBOM ScanSBOM(ctx context.Context, opts flag.Options) (types.Report, error) + // ScanVM scans VM + ScanVM(ctx context.Context, opts flag.Options) (types.Report, error) // Filter filter a report Filter(ctx context.Context, opts flag.Options, report types.Report) (types.Report, error) // Report a writes a report @@ -224,6 +227,22 @@ func (r *runner) ScanSBOM(ctx context.Context, opts flag.Options) (types.Report, return r.scanArtifact(ctx, opts, s) } +func (r *runner) ScanVM(ctx context.Context, opts flag.Options) (types.Report, error) { + // TODO: Does VM scan disable lock file..? + opts.DisabledAnalyzers = analyzer.TypeLockfiles + + var s InitializeScanner + if opts.ServerAddr == "" { + // Scan virtual machine in standalone mode + s = vmStandaloneScanner + } else { + // Scan virtual machine in client/server mode + s = vmRemoteScanner + } + + return r.scanArtifact(ctx, opts, s) +} + func (r *runner) scanArtifact(ctx context.Context, opts flag.Options, initializeScanner InitializeScanner) (types.Report, error) { report, err := scan(ctx, opts, initializeScanner, r.cache) if err != nil { @@ -385,6 +404,10 @@ func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err err if report, err = r.ScanSBOM(ctx, opts); err != nil { return xerrors.Errorf("sbom scan error: %w", err) } + case TargetVM: + if report, err = r.ScanVM(ctx, opts); err != nil { + return xerrors.Errorf("vm scan error: %w", err) + } } report, err = r.Filter(ctx, opts, report) diff --git a/pkg/commands/artifact/scanner.go b/pkg/commands/artifact/scanner.go index f5cd1c8ee1..e510f1c845 100644 --- a/pkg/commands/artifact/scanner.go +++ b/pkg/commands/artifact/scanner.go @@ -111,3 +111,22 @@ func sbomRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner } return s, cleanup, nil } + +// vmStandaloneScanner initializes a VM scanner in standalone mode +func vmStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { + s, cleanup, err := initializeVMScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, + conf.ArtifactOption) + if err != nil { + return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a vm scanner: %w", err) + } + return s, cleanup, nil +} + +// vmRemoteScanner initializes a VM scanner in client/server mode +func vmRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { + s, cleanup, err := initializeRemoteVMScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption, conf.ArtifactOption) + if err != nil { + return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a vm scanner: %w", err) + } + return s, cleanup, nil +} diff --git a/pkg/commands/artifact/wire_gen.go b/pkg/commands/artifact/wire_gen.go index 3bcdf164c6..a157104d58 100644 --- a/pkg/commands/artifact/wire_gen.go +++ b/pkg/commands/artifact/wire_gen.go @@ -17,6 +17,7 @@ import ( local2 "github.com/aquasecurity/trivy/pkg/fanal/artifact/local" "github.com/aquasecurity/trivy/pkg/fanal/artifact/remote" "github.com/aquasecurity/trivy/pkg/fanal/artifact/sbom" + "github.com/aquasecurity/trivy/pkg/fanal/artifact/vm" "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/image" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -129,6 +130,22 @@ func initializeSBOMScanner(ctx context.Context, filePath string, artifactCache c }, nil } +func initializeVMScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.Cache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + v := _wireValue + applierApplier := applier.NewApplier(localArtifactCache, v...) + detector := ospkg.Detector{} + config := db.Config{} + client := vulnerability.NewClient(config) + localScanner := local.NewScanner(applierApplier, detector, client) + artifactArtifact, err := vm.NewArtifact(filePath, artifactCache, artifactOption) + if err != nil { + return scanner.Scanner{}, nil, err + } + scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) + return scannerScanner, func() { + }, nil +} + // 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) (scanner.Scanner, func(), error) { @@ -197,3 +214,16 @@ func initializeRemoteSBOMScanner(ctx context.Context, path string, artifactCache return scannerScanner, func() { }, nil } + +// initializeRemoteVMScanner is for vm scanning in client/server mode +func initializeRemoteVMScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + v := _wireValue3 + clientScanner := client.NewScanner(remoteScanOptions, v...) + artifactArtifact, err := vm.NewArtifact(path, artifactCache, artifactOption) + if err != nil { + return scanner.Scanner{}, nil, err + } + scannerScanner := scanner.NewScanner(clientScanner, artifactArtifact) + return scannerScanner, func() { + }, nil +} diff --git a/pkg/fanal/analyzer/language/golang/binary/binary.go b/pkg/fanal/analyzer/language/golang/binary/binary.go index 474543bc3c..b08ef1be13 100644 --- a/pkg/fanal/analyzer/language/golang/binary/binary.go +++ b/pkg/fanal/analyzer/language/golang/binary/binary.go @@ -28,7 +28,7 @@ func (a gobinaryLibraryAnalyzer) Analyze(_ context.Context, input analyzer.Analy if errors.Is(err, binary.ErrUnrecognizedExe) || errors.Is(err, binary.ErrNonGoBinary) { return nil, nil } else if err != nil { - return nil, xerrors.Errorf("go binary parse error: %w", err) + return nil, xerrors.Errorf("go binary (filepath: %s) parse error: %w", input.FilePath, err) } return language.ToAnalysisResult(types.GoBinary, input.FilePath, "", libs, deps), nil diff --git a/pkg/fanal/artifact/image/image.go b/pkg/fanal/artifact/image/image.go index 672fc9b8fe..36c9c5fe4b 100644 --- a/pkg/fanal/artifact/image/image.go +++ b/pkg/fanal/artifact/image/image.go @@ -12,7 +12,6 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "golang.org/x/exp/slices" - "golang.org/x/sync/semaphore" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" @@ -22,10 +21,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/log" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/walker" -) - -const ( - parallel = 5 + "github.com/aquasecurity/trivy/pkg/semaphore" ) type Artifact struct { @@ -205,12 +201,7 @@ func (a Artifact) consolidateCreatedBy(diffIDs, layerKeys []string, configFile * func (a Artifact) inspect(ctx context.Context, missingImage string, layerKeys, baseDiffIDs []string, layerKeyMap map[string]LayerInfo) error { done := make(chan struct{}) errCh := make(chan error) - - limit := semaphore.NewWeighted(parallel) - if a.artifactOption.Slow { - // Inspect layers in series - limit = semaphore.NewWeighted(1) - } + limit := semaphore.New(a.artifactOption.Slow) var osFound types.OS for _, k := range layerKeys { @@ -279,11 +270,7 @@ func (a Artifact) inspectLayer(ctx context.Context, layerInfo LayerInfo, disable var wg sync.WaitGroup opts := analyzer.AnalysisOptions{Offline: a.artifactOption.Offline} result := analyzer.NewAnalysisResult() - limit := semaphore.NewWeighted(parallel) - if a.artifactOption.Slow { - // Analyze files in series - limit = semaphore.NewWeighted(1) - } + limit := semaphore.New(a.artifactOption.Slow) // Walk a tar layer opqDirs, whFiles, err := a.walker.Walk(r, func(filePath string, info os.FileInfo, opener analyzer.Opener) error { diff --git a/pkg/fanal/artifact/local/fs.go b/pkg/fanal/artifact/local/fs.go index b633cbb83d..caf7722821 100644 --- a/pkg/fanal/artifact/local/fs.go +++ b/pkg/fanal/artifact/local/fs.go @@ -10,7 +10,6 @@ import ( "sync" "github.com/opencontainers/go-digest" - "golang.org/x/sync/semaphore" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" @@ -19,10 +18,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/handler" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/walker" -) - -const ( - parallel = 10 + "github.com/aquasecurity/trivy/pkg/semaphore" ) type Artifact struct { @@ -78,11 +74,7 @@ func buildAbsPaths(base string, paths []string) []string { func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) { var wg sync.WaitGroup result := analyzer.NewAnalysisResult() - limit := semaphore.NewWeighted(parallel) - if a.artifactOption.Slow { - // Analyze files in series - limit = semaphore.NewWeighted(1) - } + limit := semaphore.New(a.artifactOption.Slow) err := a.walker.Walk(a.rootPath, func(filePath string, info os.FileInfo, opener analyzer.Opener) error { directory := a.rootPath diff --git a/pkg/fanal/artifact/vm/ami.go b/pkg/fanal/artifact/vm/ami.go new file mode 100644 index 0000000000..751fd4be3c --- /dev/null +++ b/pkg/fanal/artifact/vm/ami.go @@ -0,0 +1,66 @@ +package vm + +import ( + "context" + + "github.com/aquasecurity/trivy/pkg/fanal/types" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/log" +) + +type AMI struct { + *EBS + + imageID string +} + +func newAMI(imageID string, storage Storage) (*AMI, error) { + // TODO: propagate context + ctx := context.TODO() + cfg, err := config.LoadDefaultConfig(ctx) + if err != nil { + return nil, xerrors.Errorf("aws config load error: %w", err) + } + client := ec2.NewFromConfig(cfg) + output, err := client.DescribeImages(ctx, &ec2.DescribeImagesInput{ + ImageIds: []string{imageID}, + }) + if err != nil { + return nil, xerrors.Errorf("ec2.DescribeImages: %w", err) + } else if len(output.Images) == 0 { + return nil, xerrors.Errorf("%s not found", imageID) + } + + // Take the first snapshot + for _, mapping := range output.Images[0].BlockDeviceMappings { + snapshotID := aws.ToString(mapping.Ebs.SnapshotId) + if snapshotID == "" { + continue + } + log.Logger.Infof("Snapshot %s found", snapshotID) + ebs, err := newEBS(snapshotID, storage) + if err != nil { + return nil, xerrors.Errorf("new EBS error: %w", err) + } + return &AMI{ + EBS: ebs, + imageID: imageID, + }, nil + } + + return nil, xerrors.New("no snapshot found") +} + +func (a *AMI) Inspect(ctx context.Context) (types.ArtifactReference, error) { + ref, err := a.EBS.Inspect(ctx) + if err != nil { + return types.ArtifactReference{}, err + } + ref.Name = a.imageID + return ref, nil +} diff --git a/pkg/fanal/artifact/vm/ebs.go b/pkg/fanal/artifact/vm/ebs.go new file mode 100644 index 0000000000..0628908f49 --- /dev/null +++ b/pkg/fanal/artifact/vm/ebs.go @@ -0,0 +1,120 @@ +package vm + +import ( + "context" + "io" + + lru "github.com/hashicorp/golang-lru" + ebsfile "github.com/masahiro331/go-ebs-file" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" +) + +// default block size 512 KB +// Max cache memory size 64 MB +const storageEBSCacheSize = 128 + +// EBS represents an artifact for AWS EBS snapshots +type EBS struct { + Storage + snapshotID string + ebs ebsfile.EBSAPI +} + +func newEBS(snapshotID string, vm Storage) (*EBS, error) { + ebs, err := ebsfile.New(ebsfile.Option{}) + if err != nil { + return nil, xerrors.Errorf("new ebsfile error: %w", err) + } + + return &EBS{ + Storage: vm, + snapshotID: snapshotID, + ebs: ebs, + }, nil +} + +func (a *EBS) Inspect(ctx context.Context) (types.ArtifactReference, error) { + sr, err := a.openEBS(ctx) + if err != nil { + return types.ArtifactReference{}, xerrors.Errorf("EBS open error: %w", err) + } + + cacheKey, err := a.calcCacheKey(a.snapshotID) + if err != nil { + return types.ArtifactReference{}, xerrors.Errorf("cache key calculation error: %w", err) + } + + if a.hasCache(cacheKey) { + return types.ArtifactReference{ + Name: a.snapshotID, + Type: types.ArtifactVM, + ID: cacheKey, // use a cache key as pseudo artifact ID + BlobIDs: []string{cacheKey}, + }, nil + } + + blobInfo, err := a.Analyze(ctx, sr) + if err != nil { + return types.ArtifactReference{}, xerrors.Errorf("inspection error: %w", err) + } + + if err = a.cache.PutBlob(cacheKey, blobInfo); err != nil { + return types.ArtifactReference{}, xerrors.Errorf("failed to store blob (%s) in cache: %w", cacheKey, err) + } + + return types.ArtifactReference{ + Name: a.snapshotID, + Type: types.ArtifactVM, + ID: cacheKey, // use a cache key as pseudo artifact ID + BlobIDs: []string{cacheKey}, + }, nil +} + +func (a *EBS) openEBS(ctx context.Context) (*io.SectionReader, error) { + c, err := lru.New(storageEBSCacheSize) + if err != nil { + return nil, xerrors.Errorf("lru cache error: %w", err) + } + + r, err := ebsfile.Open(a.snapshotID, ctx, c, a.ebs) + if err != nil { + return nil, xerrors.Errorf("EBS error: %w", err) + } + return r, nil +} + +func (a *EBS) Clean(_ types.ArtifactReference) error { + return nil +} + +func (a *EBS) SetEBS(ebs ebsfile.EBSAPI) { + a.ebs = ebs +} + +func (a *EBS) calcCacheKey(key string) (string, error) { + s, err := cache.CalcKey(key, a.analyzer.AnalyzerVersions(), a.handlerManager.Versions(), a.artifactOption) + if err != nil { + return "", xerrors.Errorf("failed to calculate cache key: %w", err) + } + return s, nil +} + +func (a *EBS) hasCache(cacheKey string) bool { + _, missingCacheKeys, err := a.cache.MissingBlobs(cacheKey, []string{cacheKey}) + if err != nil { + log.Logger.Debugf("Unable to query missing cache: %s", err) + return false + } + + // Cache exists + if len(missingCacheKeys) == 0 { + return true + } + + log.Logger.Debugf("Missing virtual machine cache: %s", cacheKey) + return false +} diff --git a/pkg/fanal/artifact/vm/file.go b/pkg/fanal/artifact/vm/file.go new file mode 100644 index 0000000000..262f3ac210 --- /dev/null +++ b/pkg/fanal/artifact/vm/file.go @@ -0,0 +1,112 @@ +package vm + +import ( + "context" + "crypto/sha256" + "encoding/json" + "errors" + "io" + "os" + + lru "github.com/hashicorp/golang-lru" + "github.com/opencontainers/go-digest" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/vm" + "github.com/aquasecurity/trivy/pkg/fanal/vm/disk" + "github.com/aquasecurity/trivy/pkg/log" +) + +// default vmdk block size 64 KB +// If vm type vmdk max cache memory size 64 MB +const storageFILECacheSize = 1024 + +// ImageFile represents an local VM image file +type ImageFile struct { + Storage + + filePath string + file *os.File + reader *io.SectionReader +} + +func newFile(filePath string, storage Storage) (*ImageFile, error) { + f, err := os.Open(filePath) + if err != nil { + return nil, xerrors.Errorf("file open error: %w", err) + } + + c, err := lru.New(storageFILECacheSize) + if err != nil { + return nil, xerrors.Errorf("failed to create new lru cache: %w", err) + } + + reader, err := disk.New(f, c) + if err != nil { + if errors.Is(err, vm.ErrUnsupportedType) { + return nil, err + } + + log.Logger.Debugf("VM image not detected: %s", err) + log.Logger.Debugf("Assume raw image") + fi, err := f.Stat() + if err != nil { + return nil, xerrors.Errorf("file stat error: %w", err) + } + reader = io.NewSectionReader(f, 0, fi.Size()) + } + + return &ImageFile{ + Storage: storage, + + filePath: filePath, + file: f, + reader: reader, + }, nil +} + +func (a *ImageFile) Inspect(ctx context.Context) (types.ArtifactReference, error) { + blobInfo, err := a.Analyze(ctx, a.reader) + if err != nil { + return types.ArtifactReference{}, xerrors.Errorf("inspection error: %w", err) + } + + cacheKey, err := a.calcCacheKey(blobInfo) + if err != nil { + return types.ArtifactReference{}, xerrors.Errorf("cache calculation error: %w", err) + } + + if err = a.cache.PutBlob(cacheKey, blobInfo); err != nil { + return types.ArtifactReference{}, xerrors.Errorf("failed to store blob (%s) in cache: %w", cacheKey, err) + } + + return types.ArtifactReference{ + Name: a.filePath, + Type: types.ArtifactVM, + ID: cacheKey, // use a cache key as pseudo artifact ID + BlobIDs: []string{cacheKey}, + }, nil +} + +func (a *ImageFile) calcCacheKey(blobInfo types.BlobInfo) (string, error) { + // calculate hash of JSON and use it as pseudo artifactID and blobID + h := sha256.New() + if err := json.NewEncoder(h).Encode(blobInfo); err != nil { + return "", xerrors.Errorf("json error: %w", err) + } + + d := digest.NewDigest(digest.SHA256, h) + cacheKey, err := cache.CalcKey(d.String(), a.analyzer.AnalyzerVersions(), a.handlerManager.Versions(), a.artifactOption) + if err != nil { + return "", xerrors.Errorf("cache key: %w", err) + } + + return cacheKey, nil +} + +func (a *ImageFile) Clean(reference types.ArtifactReference) error { + _ = a.file.Close() + return a.cache.DeleteBlobs(reference.BlobIDs) +} diff --git a/pkg/fanal/artifact/vm/testdata/AmazonLinux2.img.gz b/pkg/fanal/artifact/vm/testdata/AmazonLinux2.img.gz new file mode 100644 index 0000000000..584051b2a3 Binary files /dev/null and b/pkg/fanal/artifact/vm/testdata/AmazonLinux2.img.gz differ diff --git a/pkg/fanal/artifact/vm/testdata/monolithicSparse.vmdk b/pkg/fanal/artifact/vm/testdata/monolithicSparse.vmdk new file mode 100644 index 0000000000..b64f95dbde Binary files /dev/null and b/pkg/fanal/artifact/vm/testdata/monolithicSparse.vmdk differ diff --git a/pkg/fanal/artifact/vm/testdata/rawdata.img b/pkg/fanal/artifact/vm/testdata/rawdata.img new file mode 100644 index 0000000000..69d29d3c6c --- /dev/null +++ b/pkg/fanal/artifact/vm/testdata/rawdata.img @@ -0,0 +1 @@ +package testdata diff --git a/pkg/fanal/artifact/vm/vm.go b/pkg/fanal/artifact/vm/vm.go new file mode 100644 index 0000000000..82c31fc893 --- /dev/null +++ b/pkg/fanal/artifact/vm/vm.go @@ -0,0 +1,138 @@ +package vm + +import ( + "context" + "io" + "os" + "strings" + "sync" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/handler" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/walker" + "github.com/aquasecurity/trivy/pkg/semaphore" +) + +type Type string + +func (t Type) Prefix() string { + return string(t) + ":" +} + +const ( + TypeAMI Type = "ami" + TypeEBS Type = "ebs" + TypeFile Type = "file" +) + +type Storage struct { + cache cache.ArtifactCache + analyzer analyzer.AnalyzerGroup + handlerManager handler.Manager + walker walker.VM + + artifactOption artifact.Option +} + +func (a *Storage) Analyze(ctx context.Context, r *io.SectionReader) (types.BlobInfo, error) { + var wg sync.WaitGroup + limit := semaphore.New(a.artifactOption.Slow) + result := analyzer.NewAnalysisResult() + + // TODO: Always walk from the root directory. Consider whether there is a need to be able to set optional + err := a.walker.Walk(r, "/", func(filePath string, info os.FileInfo, opener analyzer.Opener) error { + opts := analyzer.AnalysisOptions{Offline: a.artifactOption.Offline} + path := strings.TrimPrefix(filePath, "/") + if err := a.analyzer.AnalyzeFile(ctx, &wg, limit, result, "/", path, info, opener, nil, opts); err != nil { + return xerrors.Errorf("analyze file (%s): %w", path, err) + } + return nil + }) + + // Wait for all the goroutine to finish. + wg.Wait() + + if err != nil { + return types.BlobInfo{}, xerrors.Errorf("walk vm error: %w", err) + } + + result.Sort() + + blobInfo := types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + OS: result.OS, + Repository: result.Repository, + PackageInfos: result.PackageInfos, + Applications: result.Applications, + Secrets: result.Secrets, + Licenses: result.Licenses, + CustomResources: result.CustomResources, + } + + if err = a.handlerManager.PostHandle(ctx, result, &blobInfo); err != nil { + return types.BlobInfo{}, xerrors.Errorf("failed to call hooks: %w", err) + } + + return blobInfo, nil +} + +func NewArtifact(target string, c cache.ArtifactCache, opt artifact.Option) (artifact.Artifact, error) { + handlerManager, err := handler.NewManager(opt) + if err != nil { + return nil, xerrors.Errorf("handler init error: %w", err) + } + a, err := analyzer.NewAnalyzerGroup(analyzer.AnalyzerOptions{ + Group: opt.AnalyzerGroup, + FilePatterns: opt.FilePatterns, + DisabledAnalyzers: opt.DisabledAnalyzers, + SecretScannerOption: opt.SecretScannerOption, + LicenseScannerOption: opt.LicenseScannerOption, + }) + if err != nil { + return nil, xerrors.Errorf("analyzer group error: %w", err) + } + + storage := Storage{ + cache: c, + analyzer: a, + handlerManager: handlerManager, + walker: walker.NewVM(opt.SkipFiles, opt.SkipDirs, opt.Slow), + artifactOption: opt, + } + + targetType := detectType(target) + switch targetType { + case TypeAMI: + target = strings.TrimPrefix(target, TypeAMI.Prefix()) + return newAMI(target, storage) + case TypeEBS: + target = strings.TrimPrefix(target, TypeEBS.Prefix()) + e, err := newEBS(target, storage) + if err != nil { + return nil, xerrors.Errorf("new EBS error: %w", err) + } + return e, nil + case TypeFile: + target = strings.TrimPrefix(target, TypeFile.Prefix()) + return newFile(target, storage) + } + return nil, xerrors.Errorf("unsupported format") +} + +func detectType(target string) Type { + switch { + case strings.HasPrefix(target, TypeAMI.Prefix()): + return TypeAMI + case strings.HasPrefix(target, TypeEBS.Prefix()): + return TypeEBS + case strings.HasPrefix(target, TypeFile.Prefix()): + return TypeFile + default: + return TypeFile + } +} diff --git a/pkg/fanal/artifact/vm/vm_test.go b/pkg/fanal/artifact/vm/vm_test.go new file mode 100644 index 0000000000..f62b1dbd0c --- /dev/null +++ b/pkg/fanal/artifact/vm/vm_test.go @@ -0,0 +1,453 @@ +package vm_test + +import ( + "context" + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/artifact/vm" + "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/types" + ebsfile "github.com/masahiro331/go-ebs-file" + + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/all" +) + +const ( + ebsPrefix = string(vm.TypeEBS) + ":" + filePrefix = string(vm.TypeFile) + ":" +) + +func TestNewArtifact(t *testing.T) { + tests := []struct { + name string + target string + wantErr assert.ErrorAssertionFunc + }{ + { + name: "happy path for file", + target: "testdata/rawdata.img", + wantErr: assert.NoError, + }, + { + name: "happy path for EBS", + target: "ebs:ebs-012345", + wantErr: assert.NoError, + }, + { + name: "sad path unsupported vm format", + target: "testdata/monolithicSparse.vmdk", + wantErr: func(t assert.TestingT, err error, args ...interface{}) bool { + return assert.ErrorContains(t, err, "unsupported type error") + }, + }, + { + name: "sad path file not found", + target: "testdata/no-file", + wantErr: func(t assert.TestingT, err error, args ...interface{}) bool { + return assert.ErrorContains(t, err, "no such file or directory") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := vm.NewArtifact(tt.target, nil, artifact.Option{}) + tt.wantErr(t, err, fmt.Sprintf("NewArtifact(%v, nil, nil)", tt.target)) + }) + } +} + +func TestArtifact_Inspect(t *testing.T) { + tests := []struct { + name string + filePath string + artifactOpt artifact.Option + scannerOpt config.ScannerOption + disabledAnalyzers []analyzer.Type + disabledHandlers []types.HandlerType + missingBlobsExpectation cache.ArtifactCacheMissingBlobsExpectation + putBlobExpectation cache.ArtifactCachePutBlobExpectation + putArtifactExpectations []cache.ArtifactCachePutArtifactExpectation + want types.ArtifactReference + wantErr string + }{ + { + name: "happy path for raw image", + filePath: "testdata/AmazonLinux2.img.gz", + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:d59c327eb3a3c71c8728f5e3d597b1c5dbf25adb54d7e9237a0f1c8a495032d6", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + OS: &types.OS{ + Family: "amazon", + Name: "2 (Karoo)", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/rpm/Packages", + Packages: expectPackages, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + putArtifactExpectations: []cache.ArtifactCachePutArtifactExpectation{ + { + Args: cache.ArtifactCachePutArtifactArgs{ + ArtifactID: "sha256:d59c327eb3a3c71c8728f5e3d597b1c5dbf25adb54d7e9237a0f1c8a495032d6", + ArtifactInfo: types.ArtifactInfo{ + SchemaVersion: types.ArtifactJSONSchemaVersion, + }, + }, + }, + }, + + want: types.ArtifactReference{ + Name: "testdata/AmazonLinux2.img.gz", + Type: types.ArtifactVM, + ID: "sha256:d59c327eb3a3c71c8728f5e3d597b1c5dbf25adb54d7e9237a0f1c8a495032d6", + BlobIDs: []string{ + "sha256:d59c327eb3a3c71c8728f5e3d597b1c5dbf25adb54d7e9237a0f1c8a495032d6", + }, + }, + }, + { + name: "happy path for ebs", + filePath: "ebs:ebs-012345", + missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ + Args: cache.ArtifactCacheMissingBlobsArgs{ + ArtifactID: "sha256:a0a5dc5e371203bcfe6e8d9d24c6910b3ca2fd661cb53de62b6371fc177dcb69", + BlobIDs: []string{"sha256:a0a5dc5e371203bcfe6e8d9d24c6910b3ca2fd661cb53de62b6371fc177dcb69"}, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobID: "sha256:a0a5dc5e371203bcfe6e8d9d24c6910b3ca2fd661cb53de62b6371fc177dcb69", + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + OS: &types.OS{ + Family: "amazon", + Name: "2 (Karoo)", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/rpm/Packages", + Packages: expectPackages, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + putArtifactExpectations: []cache.ArtifactCachePutArtifactExpectation{ + { + Args: cache.ArtifactCachePutArtifactArgs{ + ArtifactID: "sha256:a0a5dc5e371203bcfe6e8d9d24c6910b3ca2fd661cb53de62b6371fc177dcb69", + ArtifactInfo: types.ArtifactInfo{ + SchemaVersion: types.ArtifactJSONSchemaVersion, + }, + }, + }, + }, + want: types.ArtifactReference{ + Name: "ebs-012345", + Type: types.ArtifactVM, + ID: "sha256:a0a5dc5e371203bcfe6e8d9d24c6910b3ca2fd661cb53de62b6371fc177dcb69", + BlobIDs: []string{ + "sha256:a0a5dc5e371203bcfe6e8d9d24c6910b3ca2fd661cb53de62b6371fc177dcb69", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := new(cache.MockArtifactCache) + c.ApplyPutBlobExpectation(tt.putBlobExpectation) + c.ApplyMissingBlobsExpectation(tt.missingBlobsExpectation) + c.ApplyPutArtifactExpectations(tt.putArtifactExpectations) + + filePath := tt.filePath + if !strings.HasPrefix(tt.filePath, ebsPrefix) { + filePath = filepath.Join(t.TempDir(), "disk.img") + testutil.DecompressGzip(t, tt.filePath, filePath) + } + + a, err := vm.NewArtifact(filePath, c, tt.artifactOpt) + require.NoError(t, err) + + if aa, ok := a.(*vm.EBS); ok { + // blockSize: 512 KB, volumeSize: 40MB + ebs := ebsfile.NewMockEBS("testdata/AmazonLinux2.img.gz", 512<<10, 40<<20) + aa.SetEBS(ebs) + } + + got, err := a.Inspect(context.Background()) + if tt.wantErr != "" { + require.Error(t, err) + assert.ErrorContains(t, err, tt.wantErr) + return + } + tt.want.Name = trimPrefix(filePath) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func trimPrefix(s string) string { + s = strings.TrimPrefix(s, ebsPrefix) + s = strings.TrimPrefix(s, filePrefix) + return s +} + +/* +How to create test image with Ubuntu. + +# Create empty image +$ dd of=Linux.img count=0 seek=1 bs=41943040 + +# Create loop device +$ losetup /dev/loop5 Linux.img + +# Create partition +$ parted /dev/loop5 +(parted)$ mklabel gpt +(parted)$ mkpart primary 1MiB 2MiB +(parted)$ set 1 boot on +(parted)$ mkpart primary xfs 2MiB 100% +(parted)$ quit + +# Format XFS and mount +$ mkfs.xfs /dev/loop5p2 +$ mount /dev/loop5p2 /mnt/xfs + +# Create some files +$ mkdir /mnt/xfs/etc/ +$ cp system-release /mnt/xfs/etc/system-release + +# Un tup and unmount +$ umount /mnt/xfs +$ losetup -d /dev/loop5 +*/ + +var expectPackages = []types.Package{ + {ID: "amazon-linux-extras@1.6.7-1.amzn2.noarch", Name: "amazon-linux-extras", Version: "1.6.7", Release: "1.amzn2", Arch: "noarch", SrcName: "amazon-linux-extras", SrcVersion: "1.6.7", + SrcRelease: "1.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv2"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "coreutils@8.22-21.amzn2.x86_64", "python@2.7.14-58.amzn2.0.4.x86_64", "system-release@2-10.amzn2.x86_64"}}, + {ID: "basesystem@10.0-7.amzn2.0.1.noarch", Name: "basesystem", Version: "10.0", Release: "7.amzn2.0.1", Arch: "noarch", SrcName: "basesystem", SrcVersion: "10.0", + SrcRelease: "7.amzn2.0.1", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"Public Domain"}, DependsOn: []string{"filesystem@3.2-25.amzn2.0.4.x86_64", "setup@2.8.71-10.amzn2.noarch"}}, + {ID: "bash@4.2.46-30.amzn2.x86_64", Name: "bash", Version: "4.2.46", Release: "30.amzn2", Arch: "x86_64", SrcName: "bash", SrcVersion: "4.2.46", + SrcRelease: "30.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv3+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "ncurses-libs@6.0-8.20170212.amzn2.1.2.x86_64"}}, + {ID: "bzip2-libs@1.0.6-13.amzn2.0.2.x86_64", Name: "bzip2-libs", Version: "1.0.6", Release: "13.amzn2.0.2", Arch: "x86_64", SrcName: "bzip2", SrcVersion: "1.0.6", + SrcRelease: "13.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"BSD"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "ca-certificates@2018.2.22-70.0.amzn2.noarch", Name: "ca-certificates", Version: "2018.2.22", Release: "70.0.amzn2", Arch: "noarch", SrcName: "ca-certificates", SrcVersion: "2018.2.22", + SrcRelease: "70.0.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"Public Domain"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "p11-kit-trust@0.23.5-3.amzn2.0.2.x86_64", "p11-kit@0.23.5-3.amzn2.0.2.x86_64"}}, + {ID: "chkconfig@1.7.4-1.amzn2.0.2.x86_64", Name: "chkconfig", Version: "1.7.4", Release: "1.amzn2.0.2", Arch: "x86_64", SrcName: "chkconfig", SrcVersion: "1.7.4", + SrcRelease: "1.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv2"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "libselinux@2.5-12.amzn2.0.2.x86_64", "libsepol@2.5-8.1.amzn2.0.2.x86_64", "popt@1.13-16.amzn2.0.2.x86_64"}}, + {ID: "coreutils@8.22-21.amzn2.x86_64", Name: "coreutils", Version: "8.22", Release: "21.amzn2", Arch: "x86_64", SrcName: "coreutils", SrcVersion: "8.22", + SrcRelease: "21.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv3+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "gmp@6.0.0-15.amzn2.0.2.x86_64", "grep@2.20-3.amzn2.0.2.x86_64", "libacl@2.2.51-14.amzn2.x86_64", "libattr@2.4.46-12.amzn2.0.2.x86_64", "libcap@2.22-9.amzn2.0.2.x86_64", "libselinux@2.5-12.amzn2.0.2.x86_64", "ncurses@6.0-8.20170212.amzn2.1.2.x86_64", "openssl-libs@1.0.2k-16.amzn2.1.1.x86_64"}}, + {ID: "cpio@2.11-27.amzn2.x86_64", Name: "cpio", Version: "2.11", Release: "27.amzn2", Arch: "x86_64", SrcName: "cpio", SrcVersion: "2.11", + SrcRelease: "27.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv3+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "curl@7.61.1-9.amzn2.0.1.x86_64", Name: "curl", Version: "7.61.1", Release: "9.amzn2.0.1", Arch: "x86_64", SrcName: "curl", SrcVersion: "7.61.1", + SrcRelease: "9.amzn2.0.1", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MIT"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "libcurl@7.61.1-9.amzn2.0.1.x86_64", "libmetalink@0.1.2-7.amzn2.0.2.x86_64", "openssl-libs@1.0.2k-16.amzn2.1.1.x86_64", "zlib@1.2.7-17.amzn2.0.2.x86_64"}}, + {ID: "cyrus-sasl-lib@2.1.26-23.amzn2.x86_64", Name: "cyrus-sasl-lib", Version: "2.1.26", Release: "23.amzn2", Arch: "x86_64", SrcName: "cyrus-sasl", SrcVersion: "2.1.26", + SrcRelease: "23.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"BSD with advertising"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "krb5-libs@1.15.1-20.amzn2.0.1.x86_64", "libcom_err@1.42.9-12.amzn2.0.2.x86_64", "libcrypt@2.26-32.amzn2.0.1.x86_64", "libdb@5.3.21-24.amzn2.0.3.x86_64"}}, + {ID: "diffutils@3.3-4.amzn2.0.2.x86_64", Name: "diffutils", Version: "3.3", Release: "4.amzn2.0.2", Arch: "x86_64", SrcName: "diffutils", SrcVersion: "3.3", + SrcRelease: "4.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv3+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "info@5.1-5.amzn2.x86_64"}}, + {ID: "elfutils-libelf@0.170-4.amzn2.x86_64", Name: "elfutils-libelf", Version: "0.170", Release: "4.amzn2", Arch: "x86_64", SrcName: "elfutils", SrcVersion: "0.170", + SrcRelease: "4.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv2+ or LGPLv3+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "zlib@1.2.7-17.amzn2.0.2.x86_64"}}, + {ID: "expat@2.1.0-10.amzn2.0.2.x86_64", Name: "expat", Version: "2.1.0", Release: "10.amzn2.0.2", Arch: "x86_64", SrcName: "expat", SrcVersion: "2.1.0", + SrcRelease: "10.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MIT"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "file-libs@5.11-33.amzn2.0.2.x86_64", Name: "file-libs", Version: "5.11", Release: "33.amzn2.0.2", Arch: "x86_64", SrcName: "file", SrcVersion: "5.11", + SrcRelease: "33.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"BSD"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "zlib@1.2.7-17.amzn2.0.2.x86_64"}}, + {ID: "filesystem@3.2-25.amzn2.0.4.x86_64", Name: "filesystem", Version: "3.2", Release: "25.amzn2.0.4", Arch: "x86_64", SrcName: "filesystem", SrcVersion: "3.2", + SrcRelease: "25.amzn2.0.4", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"Public Domain"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "setup@2.8.71-10.amzn2.noarch"}}, + {ID: "findutils@4.5.11-5.amzn2.0.2.x86_64", Name: "findutils", Version: "4.5.11", Release: "5.amzn2.0.2", Arch: "x86_64", SrcName: "findutils", SrcVersion: "4.5.11", + SrcRelease: "5.amzn2.0.2", Epoch: 1, SrcEpoch: 1, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv3+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "libselinux@2.5-12.amzn2.0.2.x86_64"}}, + {ID: "gawk@4.0.2-4.amzn2.1.2.x86_64", Name: "gawk", Version: "4.0.2", Release: "4.amzn2.1.2", Arch: "x86_64", SrcName: "gawk", SrcVersion: "4.0.2", + SrcRelease: "4.amzn2.1.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv3+ and GPL and LGPLv3+ and LGPL and BSD"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "gdbm@1.13-6.amzn2.0.2.x86_64", Name: "gdbm", Version: "1.13", Release: "6.amzn2.0.2", Arch: "x86_64", SrcName: "gdbm", SrcVersion: "1.13", + SrcRelease: "6.amzn2.0.2", Epoch: 1, SrcEpoch: 1, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv3+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "ncurses-libs@6.0-8.20170212.amzn2.1.2.x86_64", "readline@6.2-10.amzn2.0.2.x86_64"}}, + {ID: "glib2@2.54.2-2.amzn2.x86_64", Name: "glib2", Version: "2.54.2", Release: "2.amzn2", Arch: "x86_64", SrcName: "glib2", SrcVersion: "2.54.2", + SrcRelease: "2.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "libffi@3.0.13-18.amzn2.0.2.x86_64", "libgcc@7.3.1-5.amzn2.0.2.x86_64", "libmount@2.30.2-2.amzn2.0.4.x86_64", "libselinux@2.5-12.amzn2.0.2.x86_64", "pcre@8.32-17.amzn2.0.2.x86_64", "shared-mime-info@1.8-4.amzn2.x86_64", "zlib@1.2.7-17.amzn2.0.2.x86_64"}}, + {ID: "glibc@2.26-32.amzn2.0.1.x86_64", Name: "glibc", Version: "2.26", Release: "32.amzn2.0.1", Arch: "x86_64", SrcName: "glibc", SrcVersion: "2.26", + SrcRelease: "32.amzn2.0.1", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+ and LGPLv2+ with exceptions and GPLv2+"}, DependsOn: []string{"basesystem@10.0-7.amzn2.0.1.noarch", "glibc-common@2.26-32.amzn2.0.1.x86_64", "glibc-minimal-langpack@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "glibc-common@2.26-32.amzn2.0.1.x86_64", Name: "glibc-common", Version: "2.26", Release: "32.amzn2.0.1", Arch: "x86_64", SrcName: "glibc", SrcVersion: "2.26", + SrcRelease: "32.amzn2.0.1", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+ and LGPLv2+ with exceptions and GPLv2+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "libselinux@2.5-12.amzn2.0.2.x86_64", "tzdata@2018i-1.amzn2.noarch"}}, + {ID: "glibc-langpack-en@2.26-32.amzn2.0.1.x86_64", Name: "glibc-langpack-en", Version: "2.26", Release: "32.amzn2.0.1", Arch: "x86_64", SrcName: "glibc", SrcVersion: "2.26", + SrcRelease: "32.amzn2.0.1", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+ and LGPLv2+ with exceptions and GPLv2+"}, DependsOn: []string{"glibc-common@2.26-32.amzn2.0.1.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "glibc-minimal-langpack@2.26-32.amzn2.0.1.x86_64", Name: "glibc-minimal-langpack", Version: "2.26", Release: "32.amzn2.0.1", Arch: "x86_64", SrcName: "glibc", SrcVersion: "2.26", + SrcRelease: "32.amzn2.0.1", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+ and LGPLv2+ with exceptions and GPLv2+"}, DependsOn: []string{"glibc-common@2.26-32.amzn2.0.1.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "gmp@6.0.0-15.amzn2.0.2.x86_64", Name: "gmp", Version: "6.0.0", Release: "15.amzn2.0.2", Arch: "x86_64", SrcName: "gmp", SrcVersion: "6.0.0", + SrcRelease: "15.amzn2.0.2", Epoch: 1, SrcEpoch: 1, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv3+ or GPLv2+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "libgcc@7.3.1-5.amzn2.0.2.x86_64", "libstdc++@7.3.1-5.amzn2.0.2.x86_64"}}, + {ID: "gnupg2@2.0.22-5.amzn2.0.3.x86_64", Name: "gnupg2", Version: "2.0.22", Release: "5.amzn2.0.3", Arch: "x86_64", SrcName: "gnupg2", SrcVersion: "2.0.22", + SrcRelease: "5.amzn2.0.3", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv3+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "bzip2-libs@1.0.6-13.amzn2.0.2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "libassuan@2.1.0-3.amzn2.0.2.x86_64", "libcurl@7.61.1-9.amzn2.0.1.x86_64", "libgcrypt@1.5.3-14.amzn2.0.2.x86_64", "libgpg-error@1.12-3.amzn2.0.3.x86_64", "openldap@2.4.44-15.amzn2.x86_64", "pinentry@0.8.1-17.amzn2.0.2.x86_64", "pth@2.0.7-23.amzn2.0.2.x86_64", "readline@6.2-10.amzn2.0.2.x86_64", "zlib@1.2.7-17.amzn2.0.2.x86_64"}}, + {ID: "gpg-pubkey@c87f5b1a-593863f8.", Name: "gpg-pubkey", Version: "c87f5b1a", Release: "593863f8", Arch: "None", SrcName: "", SrcVersion: "", + SrcRelease: "", Epoch: 0, SrcEpoch: 0, Maintainer: "", Layer: types.Layer{}, Licenses: []string{"pubkey"}}, + {ID: "gpgme@1.3.2-5.amzn2.0.2.x86_64", Name: "gpgme", Version: "1.3.2", Release: "5.amzn2.0.2", Arch: "x86_64", SrcName: "gpgme", SrcVersion: "1.3.2", + SrcRelease: "5.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "gnupg2@2.0.22-5.amzn2.0.3.x86_64", "libassuan@2.1.0-3.amzn2.0.2.x86_64", "libgpg-error@1.12-3.amzn2.0.3.x86_64"}}, + {ID: "grep@2.20-3.amzn2.0.2.x86_64", Name: "grep", Version: "2.20", Release: "3.amzn2.0.2", Arch: "x86_64", SrcName: "grep", SrcVersion: "2.20", + SrcRelease: "3.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv3+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "pcre@8.32-17.amzn2.0.2.x86_64"}}, + {ID: "info@5.1-5.amzn2.x86_64", Name: "info", Version: "5.1", Release: "5.amzn2", Arch: "x86_64", SrcName: "texinfo", SrcVersion: "5.1", + SrcRelease: "5.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv3+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "ncurses-libs@6.0-8.20170212.amzn2.1.2.x86_64", "zlib@1.2.7-17.amzn2.0.2.x86_64"}}, + {ID: "keyutils-libs@1.5.8-3.amzn2.0.2.x86_64", Name: "keyutils-libs", Version: "1.5.8", Release: "3.amzn2.0.2", Arch: "x86_64", SrcName: "keyutils", SrcVersion: "1.5.8", + SrcRelease: "3.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv2+ and LGPLv2+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "krb5-libs@1.15.1-20.amzn2.0.1.x86_64", Name: "krb5-libs", Version: "1.15.1", Release: "20.amzn2.0.1", Arch: "x86_64", SrcName: "krb5", SrcVersion: "1.15.1", + SrcRelease: "20.amzn2.0.1", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MIT"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "coreutils@8.22-21.amzn2.x86_64", "gawk@4.0.2-4.amzn2.1.2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "grep@2.20-3.amzn2.0.2.x86_64", "keyutils-libs@1.5.8-3.amzn2.0.2.x86_64", "libcom_err@1.42.9-12.amzn2.0.2.x86_64", "libselinux@2.5-12.amzn2.0.2.x86_64", "libverto@0.2.5-4.amzn2.0.2.x86_64", "openssl-libs@1.0.2k-16.amzn2.1.1.x86_64", "sed@4.2.2-5.amzn2.0.2.x86_64"}}, + {ID: "libacl@2.2.51-14.amzn2.x86_64", Name: "libacl", Version: "2.2.51", Release: "14.amzn2", Arch: "x86_64", SrcName: "acl", SrcVersion: "2.2.51", + SrcRelease: "14.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "libattr@2.4.46-12.amzn2.0.2.x86_64"}}, + {ID: "libassuan@2.1.0-3.amzn2.0.2.x86_64", Name: "libassuan", Version: "2.1.0", Release: "3.amzn2.0.2", Arch: "x86_64", SrcName: "libassuan", SrcVersion: "2.1.0", + SrcRelease: "3.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+ and GPLv3+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "libgpg-error@1.12-3.amzn2.0.3.x86_64"}}, + {ID: "libattr@2.4.46-12.amzn2.0.2.x86_64", Name: "libattr", Version: "2.4.46", Release: "12.amzn2.0.2", Arch: "x86_64", SrcName: "attr", SrcVersion: "2.4.46", + SrcRelease: "12.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "libblkid@2.30.2-2.amzn2.0.4.x86_64", Name: "libblkid", Version: "2.30.2", Release: "2.amzn2.0.4", Arch: "x86_64", SrcName: "util-linux", SrcVersion: "2.30.2", + SrcRelease: "2.amzn2.0.4", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "coreutils@8.22-21.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "libuuid@2.30.2-2.amzn2.0.4.x86_64"}}, + {ID: "libcap@2.22-9.amzn2.0.2.x86_64", Name: "libcap", Version: "2.22", Release: "9.amzn2.0.2", Arch: "x86_64", SrcName: "libcap", SrcVersion: "2.22", + SrcRelease: "9.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "libattr@2.4.46-12.amzn2.0.2.x86_64"}}, + {ID: "libcom_err@1.42.9-12.amzn2.0.2.x86_64", Name: "libcom_err", Version: "1.42.9", Release: "12.amzn2.0.2", Arch: "x86_64", SrcName: "e2fsprogs", SrcVersion: "1.42.9", + SrcRelease: "12.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MIT"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "libcrypt@2.26-32.amzn2.0.1.x86_64", Name: "libcrypt", Version: "2.26", Release: "32.amzn2.0.1", Arch: "x86_64", SrcName: "glibc", SrcVersion: "2.26", + SrcRelease: "32.amzn2.0.1", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+ and LGPLv2+ with exceptions and GPLv2+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "libcurl@7.61.1-9.amzn2.0.1.x86_64", Name: "libcurl", Version: "7.61.1", Release: "9.amzn2.0.1", Arch: "x86_64", SrcName: "curl", SrcVersion: "7.61.1", + SrcRelease: "9.amzn2.0.1", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MIT"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "krb5-libs@1.15.1-20.amzn2.0.1.x86_64", "libcom_err@1.42.9-12.amzn2.0.2.x86_64", "libidn2@2.0.4-1.amzn2.0.2.x86_64", "libnghttp2@1.31.1-1.amzn2.0.2.x86_64", "libssh2@1.4.3-12.amzn2.2.x86_64", "nss-pem@1.0.3-5.amzn2.x86_64", "openldap@2.4.44-15.amzn2.x86_64", "openssl-libs@1.0.2k-16.amzn2.1.1.x86_64", "zlib@1.2.7-17.amzn2.0.2.x86_64"}}, + {ID: "libdb@5.3.21-24.amzn2.0.3.x86_64", Name: "libdb", Version: "5.3.21", Release: "24.amzn2.0.3", Arch: "x86_64", SrcName: "libdb", SrcVersion: "5.3.21", + SrcRelease: "24.amzn2.0.3", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"BSD and LGPLv2 and Sleepycat"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "libdb-utils@5.3.21-24.amzn2.0.3.x86_64", Name: "libdb-utils", Version: "5.3.21", Release: "24.amzn2.0.3", Arch: "x86_64", SrcName: "libdb", SrcVersion: "5.3.21", + SrcRelease: "24.amzn2.0.3", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"BSD and LGPLv2 and Sleepycat"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "libdb@5.3.21-24.amzn2.0.3.x86_64"}}, + {ID: "libffi@3.0.13-18.amzn2.0.2.x86_64", Name: "libffi", Version: "3.0.13", Release: "18.amzn2.0.2", Arch: "x86_64", SrcName: "libffi", SrcVersion: "3.0.13", + SrcRelease: "18.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MIT and Public Domain"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "libgcc@7.3.1-5.amzn2.0.2.x86_64", Name: "libgcc", Version: "7.3.1", Release: "5.amzn2.0.2", Arch: "x86_64", SrcName: "gcc", SrcVersion: "7.3.1", + SrcRelease: "5.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv3+ and GPLv3+ with exceptions and GPLv2+ with exceptions and LGPLv2+ and BSD"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "libgcrypt@1.5.3-14.amzn2.0.2.x86_64", Name: "libgcrypt", Version: "1.5.3", Release: "14.amzn2.0.2", Arch: "x86_64", SrcName: "libgcrypt", SrcVersion: "1.5.3", + SrcRelease: "14.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "libgpg-error@1.12-3.amzn2.0.3.x86_64"}}, + {ID: "libgpg-error@1.12-3.amzn2.0.3.x86_64", Name: "libgpg-error", Version: "1.12", Release: "3.amzn2.0.3", Arch: "x86_64", SrcName: "libgpg-error", SrcVersion: "1.12", + SrcRelease: "3.amzn2.0.3", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "libidn2@2.0.4-1.amzn2.0.2.x86_64", Name: "libidn2", Version: "2.0.4", Release: "1.amzn2.0.2", Arch: "x86_64", SrcName: "libidn2", SrcVersion: "2.0.4", + SrcRelease: "1.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"(GPLv2+ or LGPLv3+) and GPLv3+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "libunistring@0.9.3-9.amzn2.0.2.x86_64"}}, + {ID: "libmetalink@0.1.2-7.amzn2.0.2.x86_64", Name: "libmetalink", Version: "0.1.2", Release: "7.amzn2.0.2", Arch: "x86_64", SrcName: "libmetalink", SrcVersion: "0.1.2", + SrcRelease: "7.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MIT"}, DependsOn: []string{"expat@2.1.0-10.amzn2.0.2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "libmount@2.30.2-2.amzn2.0.4.x86_64", Name: "libmount", Version: "2.30.2", Release: "2.amzn2.0.4", Arch: "x86_64", SrcName: "util-linux", SrcVersion: "2.30.2", + SrcRelease: "2.amzn2.0.4", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "libblkid@2.30.2-2.amzn2.0.4.x86_64", "libselinux@2.5-12.amzn2.0.2.x86_64", "libuuid@2.30.2-2.amzn2.0.4.x86_64"}}, + {ID: "libnghttp2@1.31.1-1.amzn2.0.2.x86_64", Name: "libnghttp2", Version: "1.31.1", Release: "1.amzn2.0.2", Arch: "x86_64", SrcName: "nghttp2", SrcVersion: "1.31.1", + SrcRelease: "1.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MIT"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "libselinux@2.5-12.amzn2.0.2.x86_64", Name: "libselinux", Version: "2.5", Release: "12.amzn2.0.2", Arch: "x86_64", SrcName: "libselinux", SrcVersion: "2.5", + SrcRelease: "12.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"Public Domain"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "libsepol@2.5-8.1.amzn2.0.2.x86_64", "pcre@8.32-17.amzn2.0.2.x86_64"}}, + {ID: "libsepol@2.5-8.1.amzn2.0.2.x86_64", Name: "libsepol", Version: "2.5", Release: "8.1.amzn2.0.2", Arch: "x86_64", SrcName: "libsepol", SrcVersion: "2.5", + SrcRelease: "8.1.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "libssh2@1.4.3-12.amzn2.2.x86_64", Name: "libssh2", Version: "1.4.3", Release: "12.amzn2.2", Arch: "x86_64", SrcName: "libssh2", SrcVersion: "1.4.3", + SrcRelease: "12.amzn2.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"BSD"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "openssl-libs@1.0.2k-16.amzn2.1.1.x86_64", "zlib@1.2.7-17.amzn2.0.2.x86_64"}}, + {ID: "libstdc++@7.3.1-5.amzn2.0.2.x86_64", Name: "libstdc++", Version: "7.3.1", Release: "5.amzn2.0.2", Arch: "x86_64", SrcName: "gcc", SrcVersion: "7.3.1", + SrcRelease: "5.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv3+ and GPLv3+ with exceptions and GPLv2+ with exceptions and LGPLv2+ and BSD"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "libgcc@7.3.1-5.amzn2.0.2.x86_64"}}, + {ID: "libtasn1@4.10-1.amzn2.0.2.x86_64", Name: "libtasn1", Version: "4.10", Release: "1.amzn2.0.2", Arch: "x86_64", SrcName: "libtasn1", SrcVersion: "4.10", + SrcRelease: "1.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv3+ and LGPLv2+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "libunistring@0.9.3-9.amzn2.0.2.x86_64", Name: "libunistring", Version: "0.9.3", Release: "9.amzn2.0.2", Arch: "x86_64", SrcName: "libunistring", SrcVersion: "0.9.3", + SrcRelease: "9.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv3+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "info@5.1-5.amzn2.x86_64"}}, + {ID: "libuuid@2.30.2-2.amzn2.0.4.x86_64", Name: "libuuid", Version: "2.30.2", Release: "2.amzn2.0.4", Arch: "x86_64", SrcName: "util-linux", SrcVersion: "2.30.2", + SrcRelease: "2.amzn2.0.4", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"BSD"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "libverto@0.2.5-4.amzn2.0.2.x86_64", Name: "libverto", Version: "0.2.5", Release: "4.amzn2.0.2", Arch: "x86_64", SrcName: "libverto", SrcVersion: "0.2.5", + SrcRelease: "4.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MIT"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "libxml2@2.9.1-6.amzn2.3.2.x86_64", Name: "libxml2", Version: "2.9.1", Release: "6.amzn2.3.2", Arch: "x86_64", SrcName: "libxml2", SrcVersion: "2.9.1", + SrcRelease: "6.amzn2.3.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MIT"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "xz-libs@5.2.2-1.amzn2.0.2.x86_64", "zlib@1.2.7-17.amzn2.0.2.x86_64"}}, + {ID: "lua@5.1.4-15.amzn2.0.2.x86_64", Name: "lua", Version: "5.1.4", Release: "15.amzn2.0.2", Arch: "x86_64", SrcName: "lua", SrcVersion: "5.1.4", + SrcRelease: "15.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MIT"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "ncurses-libs@6.0-8.20170212.amzn2.1.2.x86_64", "readline@6.2-10.amzn2.0.2.x86_64"}}, + {ID: "ncurses@6.0-8.20170212.amzn2.1.2.x86_64", Name: "ncurses", Version: "6.0", Release: "8.20170212.amzn2.1.2", Arch: "x86_64", SrcName: "ncurses", SrcVersion: "6.0", + SrcRelease: "8.20170212.amzn2.1.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MIT"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "ncurses-libs@6.0-8.20170212.amzn2.1.2.x86_64"}}, + {ID: "ncurses-base@6.0-8.20170212.amzn2.1.2.noarch", Name: "ncurses-base", Version: "6.0", Release: "8.20170212.amzn2.1.2", Arch: "noarch", SrcName: "ncurses", SrcVersion: "6.0", + SrcRelease: "8.20170212.amzn2.1.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MIT"}}, + {ID: "ncurses-libs@6.0-8.20170212.amzn2.1.2.x86_64", Name: "ncurses-libs", Version: "6.0", Release: "8.20170212.amzn2.1.2", Arch: "x86_64", SrcName: "ncurses", SrcVersion: "6.0", + SrcRelease: "8.20170212.amzn2.1.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MIT"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "ncurses-base@6.0-8.20170212.amzn2.1.2.noarch"}}, + {ID: "nspr@4.19.0-1.amzn2.x86_64", Name: "nspr", Version: "4.19.0", Release: "1.amzn2", Arch: "x86_64", SrcName: "nspr", SrcVersion: "4.19.0", + SrcRelease: "1.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MPLv2.0"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "nss@3.36.0-7.amzn2.x86_64", Name: "nss", Version: "3.36.0", Release: "7.amzn2", Arch: "x86_64", SrcName: "nss", SrcVersion: "3.36.0", + SrcRelease: "7.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MPLv2.0"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "nspr@4.19.0-1.amzn2.x86_64", "nss-pem@1.0.3-5.amzn2.x86_64", "nss-softokn@3.36.0-5.amzn2.x86_64", "nss-sysinit@3.36.0-7.amzn2.x86_64", "nss-util@3.36.0-1.amzn2.x86_64"}}, + {ID: "nss-pem@1.0.3-5.amzn2.x86_64", Name: "nss-pem", Version: "1.0.3", Release: "5.amzn2", Arch: "x86_64", SrcName: "nss-pem", SrcVersion: "1.0.3", + SrcRelease: "5.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MPLv1.1"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "nspr@4.19.0-1.amzn2.x86_64", "nss-util@3.36.0-1.amzn2.x86_64", "nss@3.36.0-7.amzn2.x86_64"}}, + {ID: "nss-softokn@3.36.0-5.amzn2.x86_64", Name: "nss-softokn", Version: "3.36.0", Release: "5.amzn2", Arch: "x86_64", SrcName: "nss-softokn", SrcVersion: "3.36.0", + SrcRelease: "5.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MPLv2.0"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "nspr@4.19.0-1.amzn2.x86_64", "nss-softokn-freebl@3.36.0-5.amzn2.x86_64", "nss-util@3.36.0-1.amzn2.x86_64", "sqlite@3.7.17-8.amzn2.0.2.x86_64"}}, + {ID: "nss-softokn-freebl@3.36.0-5.amzn2.x86_64", Name: "nss-softokn-freebl", Version: "3.36.0", Release: "5.amzn2", Arch: "x86_64", SrcName: "nss-softokn", SrcVersion: "3.36.0", + SrcRelease: "5.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MPLv2.0"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "nspr@4.19.0-1.amzn2.x86_64", "nss-util@3.36.0-1.amzn2.x86_64"}}, + {ID: "nss-sysinit@3.36.0-7.amzn2.x86_64", Name: "nss-sysinit", Version: "3.36.0", Release: "7.amzn2", Arch: "x86_64", SrcName: "nss", SrcVersion: "3.36.0", + SrcRelease: "7.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MPLv2.0"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "coreutils@8.22-21.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "nspr@4.19.0-1.amzn2.x86_64", "nss-util@3.36.0-1.amzn2.x86_64", "nss@3.36.0-7.amzn2.x86_64", "sed@4.2.2-5.amzn2.0.2.x86_64"}}, + {ID: "nss-tools@3.36.0-7.amzn2.x86_64", Name: "nss-tools", Version: "3.36.0", Release: "7.amzn2", Arch: "x86_64", SrcName: "nss", SrcVersion: "3.36.0", + SrcRelease: "7.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MPLv2.0"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "nspr@4.19.0-1.amzn2.x86_64", "nss-softokn@3.36.0-5.amzn2.x86_64", "nss-util@3.36.0-1.amzn2.x86_64", "nss@3.36.0-7.amzn2.x86_64", "zlib@1.2.7-17.amzn2.0.2.x86_64"}}, + {ID: "nss-util@3.36.0-1.amzn2.x86_64", Name: "nss-util", Version: "3.36.0", Release: "1.amzn2", Arch: "x86_64", SrcName: "nss-util", SrcVersion: "3.36.0", + SrcRelease: "1.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MPLv2.0"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "nspr@4.19.0-1.amzn2.x86_64"}}, + {ID: "openldap@2.4.44-15.amzn2.x86_64", Name: "openldap", Version: "2.4.44", Release: "15.amzn2", Arch: "x86_64", SrcName: "openldap", SrcVersion: "2.4.44", + SrcRelease: "15.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"OpenLDAP"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "coreutils@8.22-21.amzn2.x86_64", "cyrus-sasl-lib@2.1.26-23.amzn2.x86_64", "findutils@4.5.11-5.amzn2.0.2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "nspr@4.19.0-1.amzn2.x86_64", "nss-tools@3.36.0-7.amzn2.x86_64", "nss-util@3.36.0-1.amzn2.x86_64", "nss@3.36.0-7.amzn2.x86_64", "openssl-libs@1.0.2k-16.amzn2.1.1.x86_64", "rpm@4.11.3-25.amzn2.0.3.x86_64"}}, + {ID: "openssl-libs@1.0.2k-16.amzn2.1.1.x86_64", Name: "openssl-libs", Version: "1.0.2k", Release: "16.amzn2.1.1", Arch: "x86_64", SrcName: "openssl", SrcVersion: "1.0.2k", + SrcRelease: "16.amzn2.1.1", Epoch: 1, SrcEpoch: 1, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"OpenSSL"}, DependsOn: []string{"ca-certificates@2018.2.22-70.0.amzn2.noarch", "glibc@2.26-32.amzn2.0.1.x86_64", "krb5-libs@1.15.1-20.amzn2.0.1.x86_64", "libcom_err@1.42.9-12.amzn2.0.2.x86_64", "zlib@1.2.7-17.amzn2.0.2.x86_64"}}, + {ID: "p11-kit@0.23.5-3.amzn2.0.2.x86_64", Name: "p11-kit", Version: "0.23.5", Release: "3.amzn2.0.2", Arch: "x86_64", SrcName: "p11-kit", SrcVersion: "0.23.5", + SrcRelease: "3.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"BSD"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "libffi@3.0.13-18.amzn2.0.2.x86_64"}}, + {ID: "p11-kit-trust@0.23.5-3.amzn2.0.2.x86_64", Name: "p11-kit-trust", Version: "0.23.5", Release: "3.amzn2.0.2", Arch: "x86_64", SrcName: "p11-kit", SrcVersion: "0.23.5", + SrcRelease: "3.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"BSD"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "libffi@3.0.13-18.amzn2.0.2.x86_64", "libtasn1@4.10-1.amzn2.0.2.x86_64", "nss-softokn-freebl@3.36.0-5.amzn2.x86_64", "p11-kit@0.23.5-3.amzn2.0.2.x86_64"}}, + {ID: "pcre@8.32-17.amzn2.0.2.x86_64", Name: "pcre", Version: "8.32", Release: "17.amzn2.0.2", Arch: "x86_64", SrcName: "pcre", SrcVersion: "8.32", + SrcRelease: "17.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"BSD"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "libgcc@7.3.1-5.amzn2.0.2.x86_64", "libstdc++@7.3.1-5.amzn2.0.2.x86_64"}}, + {ID: "pinentry@0.8.1-17.amzn2.0.2.x86_64", Name: "pinentry", Version: "0.8.1", Release: "17.amzn2.0.2", Arch: "x86_64", SrcName: "pinentry", SrcVersion: "0.8.1", + SrcRelease: "17.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv2+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "ncurses-libs@6.0-8.20170212.amzn2.1.2.x86_64"}}, + {ID: "popt@1.13-16.amzn2.0.2.x86_64", Name: "popt", Version: "1.13", Release: "16.amzn2.0.2", Arch: "x86_64", SrcName: "popt", SrcVersion: "1.13", + SrcRelease: "16.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MIT"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "pth@2.0.7-23.amzn2.0.2.x86_64", Name: "pth", Version: "2.0.7", Release: "23.amzn2.0.2", Arch: "x86_64", SrcName: "pth", SrcVersion: "2.0.7", + SrcRelease: "23.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "pygpgme@0.3-9.amzn2.0.2.x86_64", Name: "pygpgme", Version: "0.3", Release: "9.amzn2.0.2", Arch: "x86_64", SrcName: "pygpgme", SrcVersion: "0.3", + SrcRelease: "9.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "gpgme@1.3.2-5.amzn2.0.2.x86_64", "python-libs@2.7.14-58.amzn2.0.4.x86_64", "python@2.7.14-58.amzn2.0.4.x86_64"}}, + {ID: "pyliblzma@0.5.3-11.amzn2.0.2.x86_64", Name: "pyliblzma", Version: "0.5.3", Release: "11.amzn2.0.2", Arch: "x86_64", SrcName: "pyliblzma", SrcVersion: "0.5.3", + SrcRelease: "11.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv3+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "python-libs@2.7.14-58.amzn2.0.4.x86_64", "python@2.7.14-58.amzn2.0.4.x86_64", "xz-libs@5.2.2-1.amzn2.0.2.x86_64"}}, + {ID: "python@2.7.14-58.amzn2.0.4.x86_64", Name: "python", Version: "2.7.14", Release: "58.amzn2.0.4", Arch: "x86_64", SrcName: "python", SrcVersion: "2.7.14", + SrcRelease: "58.amzn2.0.4", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"Python"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "python-libs@2.7.14-58.amzn2.0.4.x86_64"}}, + {ID: "python-iniparse@0.4-9.amzn2.noarch", Name: "python-iniparse", Version: "0.4", Release: "9.amzn2", Arch: "noarch", SrcName: "python-iniparse", SrcVersion: "0.4", + SrcRelease: "9.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"MIT"}, DependsOn: []string{"python@2.7.14-58.amzn2.0.4.x86_64"}}, + {ID: "python-libs@2.7.14-58.amzn2.0.4.x86_64", Name: "python-libs", Version: "2.7.14", Release: "58.amzn2.0.4", Arch: "x86_64", SrcName: "python", SrcVersion: "2.7.14", + SrcRelease: "58.amzn2.0.4", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"Python"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "bzip2-libs@1.0.6-13.amzn2.0.2.x86_64", "expat@2.1.0-10.amzn2.0.2.x86_64", "gdbm@1.13-6.amzn2.0.2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "libcrypt@2.26-32.amzn2.0.1.x86_64", "libdb@5.3.21-24.amzn2.0.3.x86_64", "libffi@3.0.13-18.amzn2.0.2.x86_64", "ncurses-libs@6.0-8.20170212.amzn2.1.2.x86_64", "openssl-libs@1.0.2k-16.amzn2.1.1.x86_64", "readline@6.2-10.amzn2.0.2.x86_64", "sqlite@3.7.17-8.amzn2.0.2.x86_64", "zlib@1.2.7-17.amzn2.0.2.x86_64"}}, + {ID: "python-pycurl@7.19.0-19.amzn2.0.2.x86_64", Name: "python-pycurl", Version: "7.19.0", Release: "19.amzn2.0.2", Arch: "x86_64", SrcName: "python-pycurl", SrcVersion: "7.19.0", + SrcRelease: "19.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+ or MIT"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "keyutils-libs@1.5.8-3.amzn2.0.2.x86_64", "libcurl@7.61.1-9.amzn2.0.1.x86_64", "python-libs@2.7.14-58.amzn2.0.4.x86_64", "python@2.7.14-58.amzn2.0.4.x86_64"}}, + {ID: "python-urlgrabber@3.10-8.amzn2.noarch", Name: "python-urlgrabber", Version: "3.10", Release: "8.amzn2", Arch: "noarch", SrcName: "python-urlgrabber", SrcVersion: "3.10", + SrcRelease: "8.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+"}, DependsOn: []string{"python-pycurl@7.19.0-19.amzn2.0.2.x86_64", "python@2.7.14-58.amzn2.0.4.x86_64"}}, + {ID: "pyxattr@0.5.1-5.amzn2.0.2.x86_64", Name: "pyxattr", Version: "0.5.1", Release: "5.amzn2.0.2", Arch: "x86_64", SrcName: "pyxattr", SrcVersion: "0.5.1", + SrcRelease: "5.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "libattr@2.4.46-12.amzn2.0.2.x86_64", "python-libs@2.7.14-58.amzn2.0.4.x86_64", "python@2.7.14-58.amzn2.0.4.x86_64"}}, + {ID: "readline@6.2-10.amzn2.0.2.x86_64", Name: "readline", Version: "6.2", Release: "10.amzn2.0.2", Arch: "x86_64", SrcName: "readline", SrcVersion: "6.2", + SrcRelease: "10.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv3+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "ncurses-libs@6.0-8.20170212.amzn2.1.2.x86_64"}}, + {ID: "rpm@4.11.3-25.amzn2.0.3.x86_64", Name: "rpm", Version: "4.11.3", Release: "25.amzn2.0.3", Arch: "x86_64", SrcName: "rpm", SrcVersion: "4.11.3", + SrcRelease: "25.amzn2.0.3", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv2+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "bzip2-libs@1.0.6-13.amzn2.0.2.x86_64", "coreutils@8.22-21.amzn2.x86_64", "curl@7.61.1-9.amzn2.0.1.x86_64", "elfutils-libelf@0.170-4.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "libacl@2.2.51-14.amzn2.x86_64", "libcap@2.22-9.amzn2.0.2.x86_64", "libdb@5.3.21-24.amzn2.0.3.x86_64", "libselinux@2.5-12.amzn2.0.2.x86_64", "lua@5.1.4-15.amzn2.0.2.x86_64", "nss@3.36.0-7.amzn2.x86_64", "popt@1.13-16.amzn2.0.2.x86_64", "rpm-libs@4.11.3-25.amzn2.0.3.x86_64", "xz-libs@5.2.2-1.amzn2.0.2.x86_64", "zlib@1.2.7-17.amzn2.0.2.x86_64"}}, + {ID: "rpm-build-libs@4.11.3-25.amzn2.0.3.x86_64", Name: "rpm-build-libs", Version: "4.11.3", Release: "25.amzn2.0.3", Arch: "x86_64", SrcName: "rpm", SrcVersion: "4.11.3", + SrcRelease: "25.amzn2.0.3", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv2+ and LGPLv2+ with exceptions"}, DependsOn: []string{"bzip2-libs@1.0.6-13.amzn2.0.2.x86_64", "elfutils-libelf@0.170-4.amzn2.x86_64", "file-libs@5.11-33.amzn2.0.2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "libacl@2.2.51-14.amzn2.x86_64", "libcap@2.22-9.amzn2.0.2.x86_64", "libdb@5.3.21-24.amzn2.0.3.x86_64", "libselinux@2.5-12.amzn2.0.2.x86_64", "lua@5.1.4-15.amzn2.0.2.x86_64", "nss@3.36.0-7.amzn2.x86_64", "popt@1.13-16.amzn2.0.2.x86_64", "rpm-libs@4.11.3-25.amzn2.0.3.x86_64", "xz-libs@5.2.2-1.amzn2.0.2.x86_64", "zlib@1.2.7-17.amzn2.0.2.x86_64"}}, + {ID: "rpm-libs@4.11.3-25.amzn2.0.3.x86_64", Name: "rpm-libs", Version: "4.11.3", Release: "25.amzn2.0.3", Arch: "x86_64", SrcName: "rpm", SrcVersion: "4.11.3", + SrcRelease: "25.amzn2.0.3", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv2+ and LGPLv2+ with exceptions"}, DependsOn: []string{"bzip2-libs@1.0.6-13.amzn2.0.2.x86_64", "elfutils-libelf@0.170-4.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "libacl@2.2.51-14.amzn2.x86_64", "libcap@2.22-9.amzn2.0.2.x86_64", "libdb@5.3.21-24.amzn2.0.3.x86_64", "libselinux@2.5-12.amzn2.0.2.x86_64", "lua@5.1.4-15.amzn2.0.2.x86_64", "nss@3.36.0-7.amzn2.x86_64", "popt@1.13-16.amzn2.0.2.x86_64", "rpm@4.11.3-25.amzn2.0.3.x86_64", "xz-libs@5.2.2-1.amzn2.0.2.x86_64", "zlib@1.2.7-17.amzn2.0.2.x86_64"}}, + {ID: "rpm-python@4.11.3-25.amzn2.0.3.x86_64", Name: "rpm-python", Version: "4.11.3", Release: "25.amzn2.0.3", Arch: "x86_64", SrcName: "rpm", SrcVersion: "4.11.3", + SrcRelease: "25.amzn2.0.3", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv2+"}, DependsOn: []string{"bzip2-libs@1.0.6-13.amzn2.0.2.x86_64", "elfutils-libelf@0.170-4.amzn2.x86_64", "file-libs@5.11-33.amzn2.0.2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "libacl@2.2.51-14.amzn2.x86_64", "libcap@2.22-9.amzn2.0.2.x86_64", "libdb@5.3.21-24.amzn2.0.3.x86_64", "libselinux@2.5-12.amzn2.0.2.x86_64", "lua@5.1.4-15.amzn2.0.2.x86_64", "nss@3.36.0-7.amzn2.x86_64", "popt@1.13-16.amzn2.0.2.x86_64", "python-libs@2.7.14-58.amzn2.0.4.x86_64", "python@2.7.14-58.amzn2.0.4.x86_64", "rpm-build-libs@4.11.3-25.amzn2.0.3.x86_64", "rpm-libs@4.11.3-25.amzn2.0.3.x86_64", "rpm@4.11.3-25.amzn2.0.3.x86_64", "xz-libs@5.2.2-1.amzn2.0.2.x86_64", "zlib@1.2.7-17.amzn2.0.2.x86_64"}}, + {ID: "sed@4.2.2-5.amzn2.0.2.x86_64", Name: "sed", Version: "4.2.2", Release: "5.amzn2.0.2", Arch: "x86_64", SrcName: "sed", SrcVersion: "4.2.2", + SrcRelease: "5.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv3+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "libselinux@2.5-12.amzn2.0.2.x86_64"}}, + {ID: "setup@2.8.71-10.amzn2.noarch", Name: "setup", Version: "2.8.71", Release: "10.amzn2", Arch: "noarch", SrcName: "setup", SrcVersion: "2.8.71", + SrcRelease: "10.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"Public Domain"}, DependsOn: []string{"system-release@2-10.amzn2.x86_64"}}, + {ID: "shared-mime-info@1.8-4.amzn2.x86_64", Name: "shared-mime-info", Version: "1.8", Release: "4.amzn2", Arch: "x86_64", SrcName: "shared-mime-info", SrcVersion: "1.8", + SrcRelease: "4.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv2+"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64", "coreutils@8.22-21.amzn2.x86_64", "glib2@2.54.2-2.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "libxml2@2.9.1-6.amzn2.3.2.x86_64"}}, + {ID: "sqlite@3.7.17-8.amzn2.0.2.x86_64", Name: "sqlite", Version: "3.7.17", Release: "8.amzn2.0.2", Arch: "x86_64", SrcName: "sqlite", SrcVersion: "3.7.17", + SrcRelease: "8.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"Public Domain"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "ncurses-libs@6.0-8.20170212.amzn2.1.2.x86_64", "readline@6.2-10.amzn2.0.2.x86_64"}}, + {ID: "system-release@2-10.amzn2.x86_64", Name: "system-release", Version: "2", Release: "10.amzn2", Arch: "x86_64", SrcName: "system-release", SrcVersion: "2", + SrcRelease: "10.amzn2", Epoch: 1, SrcEpoch: 1, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv2"}, DependsOn: []string{"bash@4.2.46-30.amzn2.x86_64"}}, + {ID: "tzdata@2018i-1.amzn2.noarch", Name: "tzdata", Version: "2018i", Release: "1.amzn2", Arch: "noarch", SrcName: "tzdata", SrcVersion: "2018i", + SrcRelease: "1.amzn2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"Public Domain"}}, + {ID: "vim-minimal@7.4.160-4.amzn2.0.16.x86_64", Name: "vim-minimal", Version: "7.4.160", Release: "4.amzn2.0.16", Arch: "x86_64", SrcName: "vim", SrcVersion: "7.4.160", + SrcRelease: "4.amzn2.0.16", Epoch: 2, SrcEpoch: 2, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"Vim"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64", "libacl@2.2.51-14.amzn2.x86_64", "libselinux@2.5-12.amzn2.0.2.x86_64", "ncurses-libs@6.0-8.20170212.amzn2.1.2.x86_64"}}, + {ID: "xz-libs@5.2.2-1.amzn2.0.2.x86_64", Name: "xz-libs", Version: "5.2.2", Release: "1.amzn2.0.2", Arch: "x86_64", SrcName: "xz", SrcVersion: "5.2.2", + SrcRelease: "1.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"LGPLv2+"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, + {ID: "yum@3.4.3-158.amzn2.0.2.noarch", Name: "yum", Version: "3.4.3", Release: "158.amzn2.0.2", Arch: "noarch", SrcName: "yum", SrcVersion: "3.4.3", + SrcRelease: "158.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv2+"}, DependsOn: []string{"cpio@2.11-27.amzn2.x86_64", "diffutils@3.3-4.amzn2.0.2.x86_64", "pygpgme@0.3-9.amzn2.0.2.x86_64", "pyliblzma@0.5.3-11.amzn2.0.2.x86_64", "python-iniparse@0.4-9.amzn2.noarch", "python-urlgrabber@3.10-8.amzn2.noarch", "python@2.7.14-58.amzn2.0.4.x86_64", "pyxattr@0.5.1-5.amzn2.0.2.x86_64", "rpm-python@4.11.3-25.amzn2.0.3.x86_64", "rpm@4.11.3-25.amzn2.0.3.x86_64", "yum-metadata-parser@1.1.4-10.amzn2.0.2.x86_64"}}, + {ID: "yum-metadata-parser@1.1.4-10.amzn2.0.2.x86_64", Name: "yum-metadata-parser", Version: "1.1.4", Release: "10.amzn2.0.2", Arch: "x86_64", SrcName: "yum-metadata-parser", SrcVersion: "1.1.4", + SrcRelease: "10.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv2"}, DependsOn: []string{"glib2@2.54.2-2.amzn2.x86_64", "glibc@2.26-32.amzn2.0.1.x86_64", "libxml2@2.9.1-6.amzn2.3.2.x86_64", "python-libs@2.7.14-58.amzn2.0.4.x86_64", "python@2.7.14-58.amzn2.0.4.x86_64", "sqlite@3.7.17-8.amzn2.0.2.x86_64"}}, + {ID: "yum-plugin-ovl@1.1.31-46.amzn2.0.1.noarch", Name: "yum-plugin-ovl", Version: "1.1.31", Release: "46.amzn2.0.1", Arch: "noarch", SrcName: "yum-utils", SrcVersion: "1.1.31", + SrcRelease: "46.amzn2.0.1", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv2+"}, DependsOn: []string{"yum@3.4.3-158.amzn2.0.2.noarch"}}, + {ID: "yum-plugin-priorities@1.1.31-46.amzn2.0.1.noarch", Name: "yum-plugin-priorities", Version: "1.1.31", Release: "46.amzn2.0.1", Arch: "noarch", SrcName: "yum-utils", SrcVersion: "1.1.31", + SrcRelease: "46.amzn2.0.1", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"GPLv2+"}, DependsOn: []string{"yum@3.4.3-158.amzn2.0.2.noarch"}}, + {ID: "zlib@1.2.7-17.amzn2.0.2.x86_64", Name: "zlib", Version: "1.2.7", Release: "17.amzn2.0.2", Arch: "x86_64", SrcName: "zlib", SrcVersion: "1.2.7", + SrcRelease: "17.amzn2.0.2", Epoch: 0, SrcEpoch: 0, Maintainer: "Amazon Linux", Layer: types.Layer{}, Licenses: []string{"zlib and Boost"}, DependsOn: []string{"glibc@2.26-32.amzn2.0.1.x86_64"}}, +} diff --git a/pkg/fanal/types/artifact.go b/pkg/fanal/types/artifact.go index 80c43f6a3a..e90beab456 100644 --- a/pkg/fanal/types/artifact.go +++ b/pkg/fanal/types/artifact.go @@ -110,6 +110,7 @@ const ( ArtifactCycloneDX ArtifactType = "cyclonedx" ArtifactSPDX ArtifactType = "spdx" ArtifactAWSAccount ArtifactType = "aws_account" + ArtifactVM ArtifactType = "vm" ) // ArtifactReference represents a reference of container image, local filesystem and repository diff --git a/pkg/fanal/vm/disk/disk.go b/pkg/fanal/vm/disk/disk.go new file mode 100644 index 0000000000..ac07ca3613 --- /dev/null +++ b/pkg/fanal/vm/disk/disk.go @@ -0,0 +1,36 @@ +package disk + +import ( + "errors" + "io" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/vm" +) + +var ( + vmDisks = []Disk{ + VMDK{}, + } +) + +// Disk defines virtual machine disk images like VMDK, VDI and VHD. +type Disk interface { + NewReader(rs io.ReadSeeker, cache vm.Cache) (*io.SectionReader, error) +} + +func New(rs io.ReadSeeker, cache vm.Cache) (*io.SectionReader, error) { + for _, disk := range vmDisks { + vreader, err := disk.NewReader(rs, cache) + if err != nil { + if errors.Is(err, vm.ErrInvalidSignature) { + continue + } + return nil, xerrors.Errorf("open virtual machine error: %w", err) + } + + return vreader, nil + } + return nil, xerrors.New("virtual machine can not be detected") +} diff --git a/pkg/fanal/vm/disk/disk_test.go b/pkg/fanal/vm/disk/disk_test.go new file mode 100644 index 0000000000..669b46e646 --- /dev/null +++ b/pkg/fanal/vm/disk/disk_test.go @@ -0,0 +1,43 @@ +package disk_test + +import ( + "io" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/vm" + "github.com/aquasecurity/trivy/pkg/fanal/vm/disk" +) + +func TestNew(t *testing.T) { + type args struct { + rs io.ReadSeeker + cache vm.Cache + } + tests := []struct { + name string + fileName string + wantErr string + }{ + { + name: "invalid vm file", + fileName: "testdata/invalid.vmdk", + wantErr: "virtual machine can not be detected", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.fileName) + require.NoError(t, err) + + _, err = disk.New(f, nil) + if err == nil { + assert.Fail(t, "required error test") + } + assert.ErrorContains(t, err, tt.wantErr) + }) + } +} diff --git a/pkg/fanal/vm/disk/testdata/invalid.vmdk b/pkg/fanal/vm/disk/testdata/invalid.vmdk new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pkg/fanal/vm/disk/vmdk.go b/pkg/fanal/vm/disk/vmdk.go new file mode 100644 index 0000000000..26861e249b --- /dev/null +++ b/pkg/fanal/vm/disk/vmdk.go @@ -0,0 +1,36 @@ +package disk + +import ( + "errors" + "io" + + "github.com/masahiro331/go-vmdk-parser/pkg/virtualization/vmdk" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/vm" +) + +type VMDK struct{} + +func (V VMDK) NewReader(rs io.ReadSeeker, cache vm.Cache) (*io.SectionReader, error) { + if _, err := rs.Seek(0, io.SeekStart); err != nil { + return nil, xerrors.Errorf("seek error: %w", err) + } + + if _, err := vmdk.Check(rs); err != nil { + return nil, vm.ErrInvalidSignature + } + + if _, err := rs.Seek(0, io.SeekStart); err != nil { + return nil, xerrors.Errorf("seek error: %w", err) + } + + reader, err := vmdk.Open(rs, cache) + if err != nil { + if errors.Is(err, vmdk.ErrUnSupportedType) { + return nil, xerrors.Errorf("%s: %w", err.Error(), vm.ErrUnsupportedType) + } + return nil, xerrors.Errorf("failed to open vmdk: %w", err) + } + return reader, nil +} diff --git a/pkg/fanal/vm/disk/vmdk_test.go b/pkg/fanal/vm/disk/vmdk_test.go new file mode 100644 index 0000000000..9d2db6d607 --- /dev/null +++ b/pkg/fanal/vm/disk/vmdk_test.go @@ -0,0 +1,41 @@ +package disk_test + +import ( + "os" + "testing" + + "github.com/aquasecurity/trivy/pkg/fanal/vm/disk" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestVMDK_NewReader(t *testing.T) { + tests := []struct { + name string + fileName string + wantErr string + }{ + // TODO: add valid tests + { + name: "invalid vmdk file", + fileName: "testdata/invalid.vmdk", + wantErr: "invalid signature error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := disk.VMDK{} + + f, err := os.Open(tt.fileName) + require.NoError(t, err) + defer f.Close() + + _, err = v.NewReader(f, nil) + if err == nil { + assert.Fail(t, "required error test") + } + assert.ErrorContains(t, err, tt.wantErr) + }) + } +} diff --git a/pkg/fanal/vm/filesystem/ext4.go b/pkg/fanal/vm/filesystem/ext4.go new file mode 100644 index 0000000000..f9b20dfa0a --- /dev/null +++ b/pkg/fanal/vm/filesystem/ext4.go @@ -0,0 +1,34 @@ +package filesystem + +import ( + "io" + "io/fs" + + "github.com/masahiro331/go-ext4-filesystem/ext4" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/vm" +) + +type EXT4 struct{} + +func (e EXT4) New(sr io.SectionReader, cache vm.Cache) (fs.FS, error) { + _, err := sr.Seek(0, io.SeekStart) + if err != nil { + return nil, xerrors.Errorf("failed to seek offset error: %w", err) + } + ok := ext4.Check(&sr) + if !ok { + return nil, ErrInvalidHeader + } + + _, err = sr.Seek(0, io.SeekStart) + if err != nil { + return nil, xerrors.Errorf("failed to seek offset error: %w", err) + } + f, err := ext4.NewFS(sr, cache) + if err != nil { + return nil, xerrors.Errorf("new ext4 filesystem error: %w", err) + } + return f, nil +} diff --git a/pkg/fanal/vm/filesystem/filesystem.go b/pkg/fanal/vm/filesystem/filesystem.go new file mode 100644 index 0000000000..9ba23ad93f --- /dev/null +++ b/pkg/fanal/vm/filesystem/filesystem.go @@ -0,0 +1,51 @@ +package filesystem + +import ( + "errors" + "io" + "io/fs" + + lru "github.com/hashicorp/golang-lru" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/vm" +) + +const cacheSize = 2048 + +var ( + filesystems = []Filesystem{ + EXT4{}, + XFS{}, + } + + ErrInvalidHeader = xerrors.New("invalid Header error") +) + +type Filesystem interface { + New(sr io.SectionReader, cache vm.Cache) (fs.FS, error) +} + +func New(sr io.SectionReader) (fs.FS, func(), error) { + var clean func() + + // Initialize LRU cache for filesystem walking + lruCache, err := lru.New(cacheSize) + if err != nil { + return nil, clean, xerrors.Errorf("failed to create a LRU cache: %w", err) + } + clean = lruCache.Purge + + for _, filesystem := range filesystems { + // TODO: implement LVM handler + fsys, err := filesystem.New(sr, lruCache) + if err != nil { + if errors.Is(err, ErrInvalidHeader) { + continue + } + return nil, clean, xerrors.Errorf("unexpected fs error: %w", err) + } + return fsys, clean, nil + } + return nil, clean, xerrors.New("unable to detect filesystem") +} diff --git a/pkg/fanal/vm/filesystem/xfs.go b/pkg/fanal/vm/filesystem/xfs.go new file mode 100644 index 0000000000..d7d57021d0 --- /dev/null +++ b/pkg/fanal/vm/filesystem/xfs.go @@ -0,0 +1,34 @@ +package filesystem + +import ( + "io" + "io/fs" + + "github.com/masahiro331/go-xfs-filesystem/xfs" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/vm" +) + +type XFS struct{} + +func (x XFS) New(sr io.SectionReader, cache vm.Cache) (fs.FS, error) { + _, err := sr.Seek(0, io.SeekStart) + if err != nil { + return nil, xerrors.Errorf("failed to seek offset error: %w", err) + } + ok := xfs.Check(&sr) + if !ok { + return nil, ErrInvalidHeader + } + + _, err = sr.Seek(0, io.SeekStart) + if err != nil { + return nil, xerrors.Errorf("failed to seek offset error: %w", err) + } + f, err := xfs.NewFS(sr, cache) + if err != nil { + return nil, xerrors.Errorf("new xfs filesystem error: %w", err) + } + return f, nil +} diff --git a/pkg/fanal/vm/vm.go b/pkg/fanal/vm/vm.go new file mode 100644 index 0000000000..998588f3d9 --- /dev/null +++ b/pkg/fanal/vm/vm.go @@ -0,0 +1,18 @@ +package vm + +import ( + "golang.org/x/xerrors" +) + +var ( + ErrInvalidSignature = xerrors.New("invalid signature error") + ErrUnsupportedType = xerrors.New("unsupported type error") +) + +type Cache interface { + // Add stores data in the cache + Add(key, value interface{}) bool + + // Get returns key's value from the cache + Get(key interface{}) (value interface{}, ok bool) +} diff --git a/pkg/fanal/walker/cached_file.go b/pkg/fanal/walker/cached_file.go new file mode 100644 index 0000000000..eed1cf4bdc --- /dev/null +++ b/pkg/fanal/walker/cached_file.go @@ -0,0 +1,85 @@ +package walker + +import ( + "bytes" + "io" + "os" + "sync" + + "golang.org/x/xerrors" + + dio "github.com/aquasecurity/go-dep-parser/pkg/io" +) + +// cachedFile represents a file cached in memory or storage according to the file size. +type cachedFile struct { + once sync.Once + err error + + size int64 + reader io.Reader + + threshold int64 //γ€€Files larger than this threshold are written to file without being read into memory. + + content []byte // It will be populated if this file is small + filePath string // It will be populated if this file is large +} + +func newCachedFile(size int64, r io.Reader, threshold int64) *cachedFile { + return &cachedFile{ + size: size, + reader: r, + threshold: threshold, + } +} + +// Open opens a file and cache the file. +// If the file size is greater than or equal to threshold, it copies the content to a temp file and opens it next time. +// If the file size is less than threshold, it opens the file once and the content will be shared so that others analyzers can use the same data. +func (o *cachedFile) Open() (dio.ReadSeekCloserAt, error) { + o.once.Do(func() { + // When the file is large, it will be written down to a temp file. + if o.size >= o.threshold { + f, err := os.CreateTemp("", "fanal-*") + if err != nil { + o.err = xerrors.Errorf("failed to create the temp file: %w", err) + return + } + + if _, err = io.Copy(f, o.reader); err != nil { + o.err = xerrors.Errorf("failed to copy: %w", err) + return + } + + o.filePath = f.Name() + } else { + b, err := io.ReadAll(o.reader) + if err != nil { + o.err = xerrors.Errorf("unable to read the file: %w", err) + return + } + o.content = b + } + }) + if o.err != nil { + return nil, xerrors.Errorf("failed to open: %w", o.err) + } + + return o.open() +} + +func (o *cachedFile) open() (dio.ReadSeekCloserAt, error) { + if o.filePath != "" { + f, err := os.Open(o.filePath) + if err != nil { + return nil, xerrors.Errorf("failed to open the temp file: %w", err) + } + return f, nil + } + + return dio.NopCloser(bytes.NewReader(o.content)), nil +} + +func (o *cachedFile) Clean() error { + return os.Remove(o.filePath) +} diff --git a/pkg/fanal/walker/tar.go b/pkg/fanal/walker/tar.go index 085ec066e5..05be3b1cd1 100644 --- a/pkg/fanal/walker/tar.go +++ b/pkg/fanal/walker/tar.go @@ -2,17 +2,12 @@ package walker import ( "archive/tar" - "bytes" "io" "io/fs" - "os" "path/filepath" "strings" - "sync" "golang.org/x/xerrors" - - dio "github.com/aquasecurity/go-dep-parser/pkg/io" ) const ( @@ -94,13 +89,13 @@ func (w LayerTar) Walk(layer io.Reader, analyzeFn WalkFunc) ([]string, []string, } func (w LayerTar) processFile(filePath string, tr *tar.Reader, fi fs.FileInfo, analyzeFn WalkFunc) error { - tf := newTarFile(fi.Size(), tr, w.threshold) + cf := newCachedFile(fi.Size(), tr, w.threshold) defer func() { // nolint - _ = tf.Clean() + _ = cf.Clean() }() - if err := analyzeFn(filePath, fi, tf.Open); err != nil { + if err := analyzeFn(filePath, fi, cf.Open); err != nil { return xerrors.Errorf("failed to analyze file: %w", err) } @@ -119,76 +114,3 @@ func underSkippedDir(filePath string, skipDirs []string) bool { } return false } - -// tarFile represents a file in a tar file. -type tarFile struct { - once sync.Once - err error - - size int64 - reader io.Reader - - threshold int64 //γ€€Files larger than this threshold are written to file without being read into memory. - - content []byte // It will be populated if this file is small - filePath string // It will be populated if this file is large -} - -func newTarFile(size int64, r io.Reader, threshold int64) tarFile { - return tarFile{ - size: size, - reader: r, - threshold: threshold, - } -} - -// Open opens a file in the tar file. -// If the file size is greater than or equal to threshold, it copies the content to a temp file and opens it next time. -// If the file size is less than threshold, it opens the file once and the content will be shared so that others analyzers can use the same data. -func (o *tarFile) Open() (dio.ReadSeekCloserAt, error) { - o.once.Do(func() { - // When the file is large, it will be written down to a temp file. - if o.size >= o.threshold { - f, err := os.CreateTemp("", "fanal-*") - if err != nil { - o.err = xerrors.Errorf("failed to create the temp file: %w", err) - return - } - - if _, err = io.Copy(f, o.reader); err != nil { - o.err = xerrors.Errorf("failed to copy: %w", err) - return - } - - o.filePath = f.Name() - } else { - b, err := io.ReadAll(o.reader) - if err != nil { - o.err = xerrors.Errorf("unable to read the file: %w", err) - return - } - o.content = b - } - }) - if o.err != nil { - return nil, xerrors.Errorf("failed to open: %w", o.err) - } - - return o.open() -} - -func (o *tarFile) open() (dio.ReadSeekCloserAt, error) { - if o.filePath != "" { - f, err := os.Open(o.filePath) - if err != nil { - return nil, xerrors.Errorf("failed to open the temp file: %w", err) - } - return f, nil - } - - return dio.NopCloser(bytes.NewReader(o.content)), nil -} - -func (o *tarFile) Clean() error { - return os.Remove(o.filePath) -} diff --git a/pkg/fanal/walker/vm.go b/pkg/fanal/walker/vm.go new file mode 100644 index 0000000000..172681d26e --- /dev/null +++ b/pkg/fanal/walker/vm.go @@ -0,0 +1,229 @@ +package walker + +import ( + "bytes" + "io" + "io/fs" + "path/filepath" + "strings" + + "github.com/masahiro331/go-disk" + "github.com/masahiro331/go-disk/gpt" + "github.com/masahiro331/go-disk/mbr" + "github.com/masahiro331/go-disk/types" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + dio "github.com/aquasecurity/go-dep-parser/pkg/io" + "github.com/aquasecurity/trivy/pkg/fanal/vm/filesystem" + "github.com/aquasecurity/trivy/pkg/log" +) + +var requiredDiskName = []string{ + "Linux", // AmazonLinux image name + "p.lxroot", // SLES image name + "primary", // Common image name + "0", // Common image name + "1", // Common image name + "2", // Common image name + "3", // Common image name +} + +func AppendPermitDiskName(s ...string) { + requiredDiskName = append(requiredDiskName, s...) +} + +type VM struct { + walker + threshold int64 + analyzeFn WalkFunc +} + +func NewVM(skipFiles, skipDirs []string, slow bool) VM { + threshold := defaultSizeThreshold + if slow { + threshold = slowSizeThreshold + } + + return VM{ + walker: newWalker(skipFiles, skipDirs, slow), + threshold: threshold, + } +} + +func (w *VM) Walk(vreader *io.SectionReader, root string, fn WalkFunc) error { + // This function will be called on each file. + w.analyzeFn = fn + + driver, err := disk.NewDriver(vreader) + if err != nil { + return xerrors.Errorf("failed to new disk driver: %w", err) + } + + for { + partition, err := driver.Next() + if err != nil { + if err == io.EOF { + break + } + return xerrors.Errorf("failed to get a next partition: %w", err) + } + + // skip boot partition + if shouldSkip(partition) { + continue + } + + // Walk each partition + if err = w.diskWalk(root, partition); err != nil { + log.Logger.Warnf("Partition error: %s", err.Error()) + } + } + return nil +} + +// Inject disk partitioning processes from externally with diskWalk. +func (w *VM) diskWalk(root string, partition types.Partition) error { + log.Logger.Debugf("Found partition: %s", partition.Name()) + + sr := partition.GetSectionReader() + + // Trivy does not support LVM scanning. It is skipped at the moment. + foundLVM, err := w.detectLVM(sr) + if err != nil { + return xerrors.Errorf("LVM detection error: %w", err) + } else if foundLVM { + log.Logger.Errorf("LVM is not supported, skip %s.img", partition.Name()) + return nil + } + + // Auto-detect filesystem such as ext4 and xfs + fsys, clean, err := filesystem.New(sr) + if err != nil { + return xerrors.Errorf("filesystem error: %w", err) + } + defer clean() + + err = fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error { + // Walk filesystem + return w.fsWalk(fsys, path, d, err) + }) + if err != nil { + return xerrors.Errorf("filesystem walk error: %w", err) + } + return nil +} + +func (w *VM) fsWalk(fsys fs.FS, path string, d fs.DirEntry, err error) error { + if err != nil { + return xerrors.Errorf("fs.Walk error: %w", err) + } + fi, err := d.Info() + if err != nil { + return xerrors.Errorf("dir entry info error: %w", err) + } + pathName := strings.TrimPrefix(filepath.Clean(path), "/") + if fi.IsDir() { + if w.shouldSkipDir(pathName) { + return filepath.SkipDir + } + return nil + } else if !fi.Mode().IsRegular() { + return nil + } else if w.shouldSkipFile(pathName) { + return nil + } else if fi.Mode()&0x1000 == 0x1000 || + fi.Mode()&0x2000 == 0x2000 || + fi.Mode()&0x6000 == 0x6000 || + fi.Mode()&0xA000 == 0xA000 || + fi.Mode()&0xc000 == 0xc000 { + // 0x1000: S_IFIFO (FIFO) + // 0x2000: S_IFCHR (Character device) + // 0x6000: S_IFBLK (Block device) + // 0xA000: S_IFLNK (Symbolic link) + // 0xC000: S_IFSOCK (Socket) + return nil + } + + cvf := newCachedVMFile(fsys, pathName, w.threshold) + defer cvf.Clean() + + if err = w.analyzeFn(path, fi, cvf.Open); err != nil { + return xerrors.Errorf("failed to analyze file: %w", err) + } + return nil +} + +type cachedVMFile struct { + fs fs.FS + filePath string + threshold int64 + + cf *cachedFile +} + +func newCachedVMFile(fsys fs.FS, filePath string, threshold int64) *cachedVMFile { + return &cachedVMFile{fs: fsys, filePath: filePath, threshold: threshold} +} + +func (cvf *cachedVMFile) Open() (dio.ReadSeekCloserAt, error) { + if cvf.cf != nil { + return cvf.cf.Open() + } + + f, err := cvf.fs.Open(cvf.filePath) + if err != nil { + return nil, xerrors.Errorf("file open error: %w", err) + } + fi, err := f.Stat() + if err != nil { + return nil, xerrors.Errorf("file stat error: %w", err) + } + + cvf.cf = newCachedFile(fi.Size(), f, cvf.threshold) + return cvf.cf.Open() +} + +func (cvf *cachedVMFile) Clean() error { + if cvf.cf == nil { + return nil + } + return cvf.cf.Clean() +} + +func (w *VM) detectLVM(sr io.SectionReader) (bool, error) { + buf := make([]byte, 512) + _, err := sr.ReadAt(buf, 512) + if err != nil { + return false, xerrors.Errorf("read header block error: %w", err) + } + _, err = sr.Seek(0, io.SeekStart) + if err != nil { + return false, xerrors.Errorf("seek error: %w", err) + } + + // LABELONE is LVM signature + if string(buf[:8]) == "LABELONE" { + return true, nil + } + return false, nil +} + +func shouldSkip(partition types.Partition) bool { + // skip empty partition + if bytes.Equal(partition.GetType(), []byte{0x00}) { + return true + } + + if !slices.Contains(requiredDiskName, partition.Name()) { + return true + } + + switch p := partition.(type) { + case *gpt.PartitionEntry: + return p.Bootable() + case *mbr.Partition: + return false + } + return false +} diff --git a/pkg/log/logger.go b/pkg/log/logger.go index d0d9d20105..1c775b37a6 100644 --- a/pkg/log/logger.go +++ b/pkg/log/logger.go @@ -3,6 +3,7 @@ package log import ( "os" + xlog "github.com/masahiro331/go-xfs-filesystem/log" "go.uber.org/zap" "go.uber.org/zap/zapcore" "golang.org/x/xerrors" @@ -36,6 +37,9 @@ func InitLogger(debug, disable bool) (err error) { // Set logger for fanal flog.SetLogger(Logger) + // Set logger for go-xfs-filesystem + xlog.SetLogger(Logger) + return nil } diff --git a/pkg/scanner/scan.go b/pkg/scanner/scan.go index 9afe5dd0aa..8d43928642 100644 --- a/pkg/scanner/scan.go +++ b/pkg/scanner/scan.go @@ -11,6 +11,7 @@ import ( flocal "github.com/aquasecurity/trivy/pkg/fanal/artifact/local" "github.com/aquasecurity/trivy/pkg/fanal/artifact/remote" "github.com/aquasecurity/trivy/pkg/fanal/artifact/sbom" + "github.com/aquasecurity/trivy/pkg/fanal/artifact/vm" "github.com/aquasecurity/trivy/pkg/fanal/image" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" @@ -64,6 +65,12 @@ var StandaloneSBOMSet = wire.NewSet( StandaloneSuperSet, ) +// StandaloneVMSet binds vm dependencies +var StandaloneVMSet = wire.NewSet( + vm.NewArtifact, + StandaloneSuperSet, +) + ///////////////// // Client/Server ///////////////// @@ -88,6 +95,12 @@ var RemoteSBOMSet = wire.NewSet( RemoteSuperSet, ) +// RemoteVMSet binds vm dependencies for client/server mode +var RemoteVMSet = wire.NewSet( + vm.NewArtifact, + RemoteSuperSet, +) + // RemoteDockerSet binds remote docker dependencies var RemoteDockerSet = wire.NewSet( aimage.NewArtifact, diff --git a/pkg/semaphore/semaphore.go b/pkg/semaphore/semaphore.go new file mode 100644 index 0000000000..f985cc5786 --- /dev/null +++ b/pkg/semaphore/semaphore.go @@ -0,0 +1,30 @@ +package semaphore + +import "golang.org/x/sync/semaphore" + +const defaultSize = 5 + +type options struct { + size int64 +} + +type option func(*options) + +func WithDefault(n int64) option { + return func(opts *options) { + opts.size = defaultSize + } +} + +func New(slow bool, opts ...option) *semaphore.Weighted { + o := &options{size: defaultSize} + for _, opt := range opts { + opt(o) + } + if slow { + // Process in series + return semaphore.NewWeighted(1) + } + // Process in parallel + return semaphore.NewWeighted(o.size) +}